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-plugin is 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 tries downloader.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 because revision is 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 a DirectRequirement because "${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 at MavenPomDownloader.download() with:
MavenDownloadingException: Unable to download POM. Version contains unresolved property placeholder.
Or, if that guard is bypassed, it reaches downloadMetadata() which calls URI.create() with ${revision} in the path, causing URISyntaxException since { and } are illegal URI characters.
Key code locations
ResolvedPom.resolveParentPom() — handles parent POM resolution
MavenPomDownloader.download() — checks for unresolved placeholders in version
MavenPomDownloader.downloadMetadata() — constructs URI for metadata download
VersionRequirement.fromVersion() — creates DirectRequirement for "${revision}"
VersionRequirement.cacheResolved() — DirectRequirement returns 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 a MavenDownloadingException and let the existing exception-handling in resolveDependencies() 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
release or latest version 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.
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 aURISyntaxExceptionorMavenDownloadingException.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
Root Cause Analysis
The problematic POM
An artifact was improperly published to a Maven repository without the
flatten-maven-pluginbeing applied. The published POM still contains raw CI-friendly placeholders:This can happen when:
flatten-maven-pluginis declared in<pluginManagement>but not referenced in<plugins>, so it never actually executes.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: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 lookupHow to Reproduce
Publish a POM to a Maven repository without flattening it:
Create a separate project that depends on
child-module: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:
${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.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.