Adding Custom Recipes to Upgrade Mappings Without Duplicating Recipe Lists
search cancel

Adding Custom Recipes to Upgrade Mappings Without Duplicating Recipe Lists

book

Article ID: 450023

calendar_today

Updated On:

Products

VMware Tanzu Platform Spring

Issue/Introduction

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"
     }
   ]
 }
  }
}

Environment

Spring Application Advisor 1.x

Cause

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.

Resolution

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:

  1. Create a custom declarative OpenRewrite recipe in YAML format (for example, custom-composite-recipe.yml):
    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

     

  2. Package the recipe as a JAR with the YAML file placed under META-INF/rewrite/ on the classpath (the standard OpenRewrite recipe-discovery location), then publish or add that JAR so Application Advisor's OpenRewrite runtime can load it — for example, as a Maven/Gradle rewrite-plugin recipe dependency, or by placing the JAR on the classpath Advisor scans when resolving mappings.
    1. Confirm the recipe name (for example, com.####.recipes.CustomSpringBootUpgrade) appears in Advisor’s resolved recipe catalog or dry-run/plan output before referencing it in the mapping file — if it’s missing, the artifact isn’t on the classpath Advisor scans.
    2. Re-run the upgrade in dry-run mode and confirm the full recipeList from the composite recipe (standard + custom recipes) is applied, not just the top-level composite name.
  3. Update the custom mapping JSON file to reference only the single composite recipe:
    {
      "slug": "spring-boot",
      "rewrite": {
    	"3.5.x": {
      	"recipes": [
        	{
          	"name": "com.####.recipes.CustomSpringBootUpgrade"
        	}
      	]
    	}
      }
    }
    

     

 

Choosing an Approach: Direct Recipe List vs. Composite Recipe Artifact


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:

  • Only one project (slug) or one version key needs the custom recipe(s).
  • The standard recipe list for that version is short, so re-typing it is low-risk.
  • You want the simplest path: no artifact packaging, no Maven coordinates, no classpath resolution — just edit the JSON referenced by SPRING_ADVISOR_MAPPING_CUSTOM_0_FILEPATH directly.

Use a composite recipe artifact when:

  • The same custom + standard recipe bundle is reused across multiple projects (slugs) or multiple version keys.
  • You want the recipe list maintained in exactly one place. With rewriteArtifacts declared once (for example in a shared global.json), every project's mapping file only ever references the composite recipe by name — never the individual recipes.
  • A future change to the standard recipe list (a Broadcom update) should require editing one YAML recipeList and bumping one artifact version, not touching every project's mapping JSON.

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.