openrewrite / openrewrite/rewrite
URISyntaxException when resolving parent POM with unresolved `${revision}` placeholder
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 3.7k
- Forks
- 570
- Avg merge
- 13h 12m
- Merged PRs (30d)
- 261
Description
Bug Description
When OpenRewrite parses a project whose transitive dependency tree includes a POM that was improperly published to a Maven repository (without flatten-maven-plugin), and that POM's <parent> version contains an unresolved CI-friendly placeholder like ${revision}, the entire resolution fails with a URISyntaxException or MavenDownloadingException.
The improperly published POM is only encountered through transitive dependency parent-chain resolution — the project being analyzed does not directly or meaningfully depend on the broken artifact. A single bad POM deep in the dependency graph should not cause the whole OpenRewrite run to fail. Maven itself handles this scenario gracefully, so OpenRewrite should ideally do the same — either resolve the version via metadata or skip the unresolvable parent and continue.
Error
Caused by: java.net.URISyntaxException: Illegal character in path at index 70:
/home/user/.m2/repository/com/example/parent-project/${revision}/parent-project-${revision}.pom
at java.net.URI$Parser.fail (URI.java:...)
at java.net.URI$Parser.checkChars (URI.java:...)
at java.net.URI$Parser.parseHierarchical (URI.java:...)
at java.net.URI$Parser.parse (URI.java:...)
at java.net.URI.<init> (URI.java:...)
at java.net.URI.create (URI.java:...)
at org.openrewrite.maven.internal.MavenPomDownloader.download (MavenPomDownloader.java:...)
at org.openrewrite.maven.tree.ResolvedPom$Resolver.resolveParentPom (ResolvedPom.java:...)
at org.openrewrite.maven.tree.ResolvedPom$Resolver.resolveParentPropertiesAndRepositoriesRecursively (ResolvedPom.java:...)
at org.openrewrite.maven.tree.ResolvedPom$Resolver.resolveParentsRecursively (ResolvedPom.java:...)
at org.openrewrite.maven.tree.ResolvedPom.resolveDependencies (ResolvedPom.java:...)
at org.openrewrite.maven.tree.MavenResolutionResult.resolveDependencies (MavenResolutionResult.java:...)
at org.openrewrite.maven.MavenParser.parseInputs (MavenParser.java:...)
Root Cause Analysis
The problematic POM
An artifact was improperly published to a Maven repository without the flatten-maven-plugin being applied. The published POM still contains raw CI-friendly placeholders:
<parent>
<groupId>com.example</groupId>
<artifactId>parent-project</artifactId>
<version>${revision}</version>
</parent>
<artifactId>child-module</artifactId>
<version>${some.version}</version>
This can happen when:
- The
flatten-maven-pluginis declared in<pluginManagement>but not referenced in<plugins>, so it never actually executes. - A single version was accidentally deployed without the flatten plugin active.
In our case, out of 160+ published versions of the artifact, only one version (2.0.16) was improperly published with the raw ${revision} placeholder. All other versions were either properly flattened or had a hardcoded parent version. This single bad version, when encountered during transitive dependency resolution, causes the entire OpenRewrite run to fail.
Why resolution fails
When OpenRewrite encounters this POM as a transitive dependency and tries to resolve its parent chain, ResolvedPom.resolveParentPom() follows this flow:
-
Raw GAV attempt:
rawGav.getVersion()is"${revision}", so it triesdownloader.download(rawGav, ...). This fails because the parent is not in the current project's reactor. -
Property resolution:
getValues(rawGav)attempts to substitute${revision}using the current POM's properties. This fails becauserevisionis defined in the parent POM's own<properties>— a chicken-and-egg problem: we need the parent to resolve the version, but we need the version to find the parent. -
VersionRequirement resolution:
VersionRequirement.fromVersion("${revision}", 0)creates aDirectRequirementbecause"${revision}"doesn't match any recognized version pattern (LATEST, RELEASE, or range).DirectRequirement.resolve()short-circuits and returns"${revision}"as-is, without ever querying repository metadata. -
Download attempt:
downloader.download(gav.withVersion("${revision}"), ...)fails atMavenPomDownloader.download()with:MavenDownloadingException: Unable to download POM. Version contains unresolved property placeholder.Or, if that guard is bypassed, it reaches
downloadMetadata()which callsURI.create()with${revision}in the path, causingURISyntaxExceptionsince{and}are illegal URI characters.
Key code locations
ResolvedPom.resolveParentPom()— handles parent POM resolutionMavenPomDownloader.download()— checks for unresolved placeholders in versionMavenPomDownloader.downloadMetadata()— constructs URI for metadata downloadVersionRequirement.fromVersion()— createsDirectRequirementfor"${revision}"VersionRequirement.cacheResolved()—DirectRequirementreturns version as-is without metadata lookup
How to Reproduce
-
Publish a POM to a Maven repository without flattening it:
<!-- parent-project/pom.xml --> <groupId>com.example</groupId> <artifactId>parent-project</artifactId> <version>${revision}</version> <properties> <revision>1.0.0</revision> </properties><!-- child-module/pom.xml (published without flatten) --> <parent> <groupId>com.example</groupId> <artifactId>parent-project</artifactId> <version>${revision}</version> </parent> <artifactId>child-module</artifactId> -
Create a separate project that depends on
child-module:<dependency> <groupId>com.example</groupId> <artifactId>child-module</artifactId> <version>1.0.0</version> </dependency> -
Run OpenRewrite on the separate project — it will fail when resolving
child-module's parent.
Expected Behavior
Since the improperly published artifact is only encountered through transitive dependency resolution and is not a direct concern of the project being analyzed, OpenRewrite should handle this gracefully rather than failing entirely. Possible approaches:
- Skip and continue: Catch the unresolvable
${revision}parent version as aMavenDownloadingExceptionand let the existing exception-handling inresolveDependencies()accumulate it as a non-fatal warning, similar to how non-classpath artifact failures are already handled. - Resolve via metadata: Query the parent artifact's repository metadata (which does not include the version in the URL path, avoiding the URI issue) and use the
releaseorlatestversion to download the parent POM.
The key point is: a single improperly published POM that the project doesn't actually depend on should not break the entire OpenRewrite run.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading ResolvedPom.resolveParentPom() and the MavenPomDownloader.download() and downloadMetadata() paths, then trace VersionRequirement.fromVersion() and cacheResolved() for unresolved placeholders. The change is done when a transitive POM with an unresolved ${revision} parent no longer causes the overall resolution to fail, while the chosen handling is covered by the project’s existing resolution behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- build-system
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100