Skip to content

Fix URISyntaxException in MavenPomDownloader for unresolved version placeholders#7313

Merged
timtebeek merged 2 commits intomainfrom
tim/fix-uri-unresolved-property
Apr 8, 2026
Merged

Fix URISyntaxException in MavenPomDownloader for unresolved version placeholders#7313
timtebeek merged 2 commits intomainfrom
tim/fix-uri-unresolved-property

Conversation

@timtebeek
Copy link
Copy Markdown
Member

Summary

  • When a parent POM version contains unresolved property placeholders like ${revision} and the parent isn't found in project POMs or via relative path, MavenPomDownloader.download() would construct a URI with illegal { } characters, causing URISyntaxException.
  • Added an early check before the repository download loop: if the version still contains ${, throw MavenDownloadingException instead of attempting to build an invalid URI.
  • Callers like ResolvedPom.resolveParentPom() already catch MavenDownloadingException for placeholder versions and fall through to property resolution, so this is handled gracefully.

Test plan

  • Existing MavenPomDownloaderTest assertions updated to expect MavenDownloadingException instead of IllegalArgumentException/Exception
  • All 61 tests in MavenPomDownloaderTest pass

…placeholders

When a parent POM version contains unresolved placeholders like ${revision}
and the parent isn't found locally, the download method would construct a URI
with illegal { } characters, causing URISyntaxException. Added an early check
to throw MavenDownloadingException instead, which callers already handle
gracefully by falling through to property resolution.
@timtebeek
Copy link
Copy Markdown
Member Author

The above is in response to the following stacktrace in recipe execution

Caused by: java.net.URISyntaxException: Illegal character in path at index 71: file:///.../foo/foo-spring-boot-app/${revision}/foo-spring-boot-app-${revision}.pom
    at java.net.URI$Parser.fail (URI.java:2995)
    at java.net.URI$Parser.checkChars (URI.java:3166)
    at java.net.URI$Parser.parseHierarchical (URI.java:3248)
    at java.net.URI$Parser.parse (URI.java:3196)
    at java.net.URI.<init> (URI.java:645)
    at java.net.URI.create (URI.java:930)
    at org.openrewrite.maven.internal.MavenPomDownloader.download (MavenPomDownloader.java:585)
    at org.openrewrite.maven.tree.ResolvedPom$Resolver.resolveParentPom (ResolvedPom.java:589)
    at org.openrewrite.maven.tree.ResolvedPom$Resolver.resolveParentPropertiesAndRepositoriesRecursively (ResolvedPom.java:477)
    at org.openrewrite.maven.tree.ResolvedPom$Resolver.resolveParentsRecursively (ResolvedPom.java:436)
    at org.openrewrite.maven.tree.ResolvedPom.resolveDependencies (ResolvedPom.java:1101)
    at org.openrewrite.maven.tree.ResolvedPom.resolveDependencies (ResolvedPom.java:999)
    at org.openrewrite.maven.tree.MavenResolutionResult.resolveDependencies (MavenResolutionResult.java:190)
    at org.openrewrite.maven.UpdateMavenModel.updateResult (UpdateMavenModel.java:184)
    at org.openrewrite.maven.UpdateMavenModel.lambda$updateResult$8 (UpdateMavenModel.java:178)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:245)
    at org.openrewrite.internal.ListUtils.map (ListUtils.java:269)
    at org.openrewrite.maven.UpdateMavenModel.updateResult (UpdateMavenModel.java:176)
    at org.openrewrite.maven.UpdateMavenModel.visitDocument (UpdateMavenModel.java:142)
    at org.openrewrite.xml.tree.Xml$Document.acceptXml (Xml.java:153)
    at org.openrewrite.xml.tree.Xml.accept (Xml.java:57)
    at org.openrewrite.TreeVisitor.visit (TreeVisitor.java:242)
    at org.openrewrite.TreeVisitor.visit (TreeVisitor.java:265)
    at org.openrewrite.TreeVisitor.visitNonNull (TreeVisitor.java:167)
    at org.openrewrite.maven.AddProperty$1.visitDocument (AddProperty.java:92)
    at org.openrewrite.maven.AddProperty$1.visitDocument (AddProperty.java:69)
    at org.openrewrite.xml.tree.Xml$Document.acceptXml (Xml.java:153)
    at org.openrewrite.xml.tree.Xml.accept (Xml.java:57)
    at org.openrewrite.TreeVisitor.visit (TreeVisitor.java:242)
    at org.openrewrite.TreeVisitor.visitNonNull (TreeVisitor.java:167)
    at org.openrewrite.maven.UpdateMavenProjectPropertyJavaVersion$1.visitDocument (UpdateMavenProjectPropertyJavaVersion.java:125)
    at org.openrewrite.maven.UpdateMavenProjectPropertyJavaVersion$1.visitDocument (UpdateMavenProjectPropertyJavaVersion.java:77)
    at org.openrewrite.xml.tree.Xml$Document.acceptXml (Xml.java:153)
    at org.openrewrite.xml.tree.Xml.accept (Xml.java:57)
    at org.openrewrite.TreeVisitor.visit (TreeVisitor.java:242)
    at org.openrewrite.TreeVisitor.visit (TreeVisitor.java:154)
    at org.openrewrite.scheduling.RecipeRunCycle.lambda$editSource$9 (RecipeRunCycle.java:355)

With the trigger on line 589 here:

private Pom resolveParentPom(Pom pom) throws MavenDownloadingException {
@SuppressWarnings("DataFlowIssue") GroupArtifactVersion rawGav = pom.getParent().getGav();
if (rawGav.getVersion() == null) {
throw new MavenParsingException("Parent version must always specify a version " + rawGav);
}
// When the parent version contains unresolved property placeholders like
// ${project.version}, the properties needed to resolve it may only be available
// from the parent itself (see MavenXpp3Reader §4.0.0, "Inherited" on <version>).
// Try to find the parent in the reactor first using the raw GAV before attempting
// property resolution. MavenPomDownloader.download() checks the reactor (exact GAV
// match, property-merged match, and relative-path match) before going remote, so
// the exception is only thrown when the parent is genuinely not a local module.
if (rawGav.getVersion().contains("${")) {
try {
return downloader.download(rawGav,
pom.getParent().getRelativePath(), ResolvedPom.this, repositories);
} catch (MavenDownloadingException ignored) {
// Parent not found in reactor with raw GAV, fall through to property resolution
}
}
GroupArtifactVersion gav = getValues(rawGav);
if (gav.getVersion() == null) {
throw new MavenParsingException("Parent version must always specify a version " + gav);
}
VersionRequirement newRequirement = VersionRequirement.fromVersion(gav.getVersion(), 0);
GroupArtifact ga = new GroupArtifact(gav.getGroupId(), gav.getArtifactId());
String newRequiredVersion = newRequirement.resolve(ga, downloader, getRepositories());
if (newRequiredVersion == null) {
throw new MavenParsingException("Could not resolve version for [" + ga + "] matching version requirements " + newRequirement);
}
gav = gav.withVersion(newRequiredVersion);
return downloader.download(gav,
pom.getParent().getRelativePath(), ResolvedPom.this, repositories);
}

var downloader = new MavenPomDownloader(pomsByPath, ctx);

assertThrows(IllegalArgumentException.class, () -> downloader.download(gav, Objects.requireNonNull(pom.getParent()).getRelativePath(), resolvedPom, singletonList(nonexistentRepo)));
assertThrows(MavenDownloadingException.class, () -> downloader.download(gav, Objects.requireNonNull(pom.getParent()).getRelativePath(), resolvedPom, singletonList(nonexistentRepo)));
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test & assertion on an IllegalArgumentException was explicitly added in

// Since the parent can't be found by GAV match either, the version still
// contains an unresolved placeholder, so download throws MavenDownloadingException
// instead of URISyntaxException from URI.create().
assertThrows(MavenDownloadingException.class,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +567 to +568
throw new MavenDownloadingException("Unable to download POM " + gav +
". Version contains unresolved property placeholder.", null, originalGav);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method is called from different paths; now checking how each treats the exception signature change from IllegalArgumentException to MavenDownloadingException:

Image

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying a few interesting patterns here:

} catch (MavenDownloadingException | MavenDownloadingExceptions | IllegalArgumentException e) {
return Markup.warn(buildScript, e);

} catch (MavenDownloadingException ignored) {
// If we can't download the POM, we can still just upgrade the dependency version
}
return dependency.withDeclaredVersion(selectedVersion).getTree();

} catch (MavenDownloadingException e) {
throw new RuntimeException(e);


} catch (MavenDownloadingException e) {
document = e.warn(document);

try {
// This is a best effort attempt to see if the pom is there anyway, in spite of the
// fact that it's not in the metadata. Usually it won't be, only in situations like the
// MapR repository mentioned in the comment above will it be.
Pom pom = new MavenPomDownloader(emptyMap(), ctx,
mrr.getMavenSettings(), mrr.getActiveProfiles()).download(new GroupArtifactVersion(groupId, artifactId, ((ExactVersion) versionComparator).getVersion()),
null, null, mrr.getPom().getRepositories());
if (pom.getGav().getVersion().equals(exactVersion) &&
!exactVersion.equals(finalVersion) &&
versionComparator.compare(finalVersion, finalVersion, exactVersion) <= 0) {
return exactVersion;
}
} catch (MavenDownloadingException e) {
return null;

The MavenDownloadingException was nearly always caught & handled (it's a checked exception after all), but the IllegalArgumentException from not being able to construct the URL would have propagated except in rare cases. This change then ensures we're more predictable at runtime for such poms that use ${revision}.

@timtebeek timtebeek requested a review from Jenson3210 April 8, 2026 08:56
Copy link
Copy Markdown
Contributor

@Jenson3210 Jenson3210 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Easy to follow change due to the good amount of rationale behind the changes.

Not sure how I feel about the comments in "prod" code being quite long even for a descriptive piece of code

if (gav.getVersion().contains("${")) { is quite self-explanatory and would at most require 1 comment placeholders in URI would also throw or even none.
Not going to trigger a new pipeline for this only comment.

@github-project-automation github-project-automation Bot moved this from In Progress to Ready to Review in OpenRewrite Apr 8, 2026
@timtebeek timtebeek merged commit 83bd388 into main Apr 8, 2026
1 check passed
@timtebeek timtebeek deleted the tim/fix-uri-unresolved-property branch April 8, 2026 09:12
@github-project-automation github-project-automation Bot moved this from Ready to Review to Done in OpenRewrite Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants