When configuring custom upgrade mappings using the environment variable SPRING_ADVISOR_MAPPING_CUSTOM_0_MERGE_STRATEGY=override, defining custom recipes under a target version key replaces the default recipe list entirely.
An attempt to append a single custom recipe results in overwriting existing recipes, requiring every standard and custom recipe to be explicitly declared in the custom mapping configuration:
{
"slug": "spring-boot",
"rewrite": {
"3.5.x": {
"recipes": [
{
"name": "com.####.example.EnableVirtualThreads"
},
{
"name": "com.vmware.tanzu.spring.recipes.boot40.UpgradeSpringBoot_4_0"
}
]
}
}
}
Spring Application Advisor 1.x
This behavior occurs by design. When setting SPRING_ADVISOR_MAPPING_CUSTOM_0_MERGE_STRATEGY to override, Application Advisor replaces the recipes, nextRewrite, and requirements fields for a specified version key as a whole. Incremental merging or appending of recipes is intentionally disabled to prevent hidden recipe drift between versions and maintain clear visibility of applied transformations.
No other merge strategy is currently supported. override is the only accepted value for SPRING_ADVISOR_MAPPING_CUSTOM_0_MERGE_STRATEGY; there is no merge or append option today. If the variable is omitted entirely, the loader treats the custom mapping file as a complete project definition and rejects it outright when the slug matches an existing built-in mapping, rather than merging or overwriting it.
To avoid maintaining a long list of individual recipes in the JSON mapping file, bundle standard and custom recipes into a single composite OpenRewrite declarative YAML recipe.
Follow these steps to implement the single-entry workaround:
type: specs.openrewrite.org/v1beta/recipe
name: com.####.recipes.CustomSpringBootUpgrade
displayName: Custom Spring Boot upgrade bundle
description: Bundles standard upgrade recipes with custom internal recipes.
recipeList:
- com.vmware.tanzu.spring.recipes.boot40.UpgradeSpringBoot_4_0
- com.####.example.EnableVirtualThreads
- com.####.example.UndertowToTomcat
{
"slug": "spring-boot",
"rewrite": {
"3.5.x": {
"recipes": [
{
"name": "com.####.recipes.CustomSpringBootUpgrade"
}
]
}
}
}
Both options above are valid; which one to use depends on how many mapping entries need the same custom recipe(s), and whether the recipe set changes often.
Use direct enumeration in the mapping JSON when:
Use a composite recipe artifact when:
Note: the composite-recipe relies on standard OpenRewrite recipe composition (a declarative YAML recipe whose recipeList references other recipes), which Advisor resolves the same way it resolves any other recipe name.