openrewrite / openrewrite/rewrite
ChangePropertyKey + UnfoldProperties produce duplicate-key (invalid) YAML when relocating multiple properties into the same new nested prefix (regression from #8291)
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 3.7k
- Forks
- 570
- Avg merge
- 13h 12m
- Merged PRs (30d)
- 261
Description
What version of OpenRewrite are you using?
rewrite-yaml 8.89.0 (via org.openrewrite.recipe:rewrite-recipe-bom 3.37.0). Confirmed working correctly on 8.87.0 (rewrite-recipe-bom 3.35.0).
How are you running OpenRewrite?
Gradle plugin, declarative YAML recipe.
What is the smallest, simplest way to reproduce the problem?
A declarative recipe with three ChangePropertyKey steps renaming distinct old properties into a shared new nested prefix, run against a YAML file that already has a top-level spring: mapping (but not yet the target nested prefix), followed by org.openrewrite.yaml.UnfoldProperties to fold the resulting flattened keys back into a nested mapping:
recipeList:
- org.openrewrite.yaml.ChangePropertyKey:
oldPropertyKey: legacy.jwks.url
newPropertyKey: spring.security.oauth2.resourceserver.jwt.jwk-set-uri
relaxedBinding: true
- org.openrewrite.yaml.ChangePropertyKey:
oldPropertyKey: legacy.jwt.issuer
newPropertyKey: spring.security.oauth2.resourceserver.jwt.issuer-uri
relaxedBinding: true
- org.openrewrite.yaml.ChangePropertyKey:
oldPropertyKey: legacy.jwt.audience
newPropertyKey: spring.security.oauth2.resourceserver.jwt.audiences
relaxedBinding: true
- org.openrewrite.yaml.UnfoldProperties:
applyTo:
- $..[security.oauth2.resourceserver.jwt]
Input:
spring:
application:
name: my-service
legacy:
jwks:
url: https://auth.example.com/jwks
jwt:
issuer: https://auth.example.com
audience: my-audience
What did you expect to see?
A single merged nested block:
spring:
application:
name: my-service
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: https://auth.example.com/jwks
issuer-uri: https://auth.example.com
audiences: my-audience
(This is what 8.87.0 produced.)
What did you see instead?
Three separate, sibling security: mapping entries under spring: — duplicate keys, which is invalid/undefined per the YAML spec, and silently drops all but the last when read by SnakeYAML/Jackson:
spring:
application:
name: my-service
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: https://auth.example.com/jwks
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://auth.example.com
security:
oauth2:
resourceserver:
jwt:
audiences: my-audience
Is there something in particular you'd like feedback on?
- This looks like a regression from #8291: since
spring:already exists as a mapping, eachChangePropertyKeynow nests its relocated key one level underspring:rather than writing it as a fully-dotted top-level flat key. That's correct in isolation for a single rename, but when three separate renames target the same new nested prefix, each one independently inserts a siblingsecurity:entry rather than recognizing and merging into the one a prior step in the same recipe run just inserted.
I also tried adding org.openrewrite.yaml.CoalesceProperties after UnfoldProperties to squash the duplicates — it does merge the content correctly, but the recipe then fails to stabilize in a single cycle (RewriteTest's "recipe should complete in one cycle" check), and also destabilizes an unrelated, previously-passing test case whose input has no pre-existing spring: tree at all — suggesting the combination is non-idempotent here, not just slower.
Any additional context?
This surfaced while chasing a version bump for rewrite-recipe-bom in a downstream project's own OpenRewrite migration recipes — happy to share the full failing recipe/test if useful.
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 with the ChangePropertyKey and UnfoldProperties entry points and reproduce the three-step recipe against the YAML input shown. Trace how each relocation is inserted beneath the existing spring mapping, then verify that the resulting YAML has one merged security mapping, remains valid, and completes stably in one recipe cycle.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100