Claude Code Plugins

Community-maintained marketplace

Feedback

writing-openrewrite-recipes

@openrewrite/rewrite-docs
48
0

Use when creating/writing/building OpenRewrite recipes, working with .java recipe files, RewriteTest files, recipe YAML files, LST visitors, JavaTemplate, visitor patterns, or when discussing recipe types (declarative YAML, Refaster templates, imperative Java recipes) - guides creation of OpenRewrite recipes for automated code transformations, AST manipulation, custom refactoring rules, and code migration.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name writing-openrewrite-recipes
description Use when creating/writing/building OpenRewrite recipes, working with .java recipe files, RewriteTest files, recipe YAML files, LST visitors, JavaTemplate, visitor patterns, or when discussing recipe types (declarative YAML, Refaster templates, imperative Java recipes) - guides creation of OpenRewrite recipes for automated code transformations, AST manipulation, custom refactoring rules, and code migration.
allowed-tools Read, Write, Edit, Bash, Grep, Glob

OpenRewrite Recipe Writing Skill

Overview

OpenRewrite recipes are automated refactoring operations that modify Lossless Semantic Trees (LSTs) representing source code. This skill guides through creating recipes efficiently and correctly.

When NOT to Use This Skill

Do NOT use this skill for:

  • General Java programming questions unrelated to OpenRewrite
  • Questions about running existing OpenRewrite recipes (use OpenRewrite documentation)
  • Build tool configuration unrelated to recipe development
  • General refactoring advice without OpenRewrite context

Quick Start Decision Tree

To determine the best approach quickly:

  1. Can the transformation be expressed by composing existing recipes? → Use Declarative YAML (see Declarative YAML Recipes section below)

  2. Is it a simple expression/statement replacement pattern? → Use Refaster Template (see Refaster Template Recipes section below)

  3. Requires complex logic, conditional transformations, or custom analysis? → Use Imperative Java Recipe (see Imperative Recipe Development Workflow below)

For imperative recipes, proceed to "Imperative Recipe Development Workflow" below.

Recipe Type Selection

Choose the appropriate recipe type based on your needs:

Declarative YAML Recipes (Preferred)

Use when: Composing existing recipes with configuration

Advantages: No code, simple, maintainable

Example use case: Combining framework migration steps

type: specs.openrewrite.org/v1beta/recipe
name: com.yourorg.MyMigration
displayName: Migrate to Framework X
recipeList:
  - org.openrewrite.java.ChangeType:
      oldFullyQualifiedTypeName: old.Type
      newFullyQualifiedTypeName: new.Type
  - com.yourorg.OtherRecipe

Finding Recipes to Use: When building declarative YAML recipes, consult the recipe catalog CSV files in the references/ directory:

  • references/recipes-top.csv - 50 commonly used recipes across all categories (best starting point)
  • references/recipes-java-basic.csv - 32 basic Java refactoring operations
  • references/recipes-spring-boot-common.csv - 60 Spring Boot migrations and best practices
  • references/recipes-framework-migrations-common.csv - 16 major framework migrations (diverse frameworks)
  • references/recipes-testing-common.csv - 60 most useful testing recipes (JUnit, Mockito, AssertJ)
  • references/recipes-dependencies-common.csv - 49 dependency operations (Maven+Gradle when possible)
  • references/recipes-security-common.csv - 30 security vulnerability detection and fixes
  • references/recipes-xml-yaml-json-common.csv - 50 configuration file operations
  • references/recipes-static-analysis-common.csv - 50 code analysis and search recipes
  • references/recipes-logging-common.csv - 50 logging framework operations
  • references/recipes-file-operations.csv - 14 file and text manipulation operations

Usage Pattern: Start with recipes-top.csv, then consult the specific category file based on what the user needs. These curated lists contain the most practical and commonly used recipes for each category.

Refaster Template Recipes

Use when: Simple expression/statement replacements

Advantages: Faster than imperative, type-aware

Example use case: Replace StringUtils.equals() with Objects.equals()

Imperative Java Recipes

Use when: Complex logic, conditional transformations, custom analysis

Advantages: Full control, complex transformations

Example use case: Add modifiers only to variables that aren't reassigned

Decision Rule: If it can be declarative, make it declarative. Use imperative only when necessary.

Examples Quick Reference

Navigate to the right example based on your needs:

  • New to recipes? Start with references/example-say-hello-recipe.java
  • Need multi-file analysis? See references/example-scanning-recipe.java
  • Composing recipes? Check references/example-declarative-migration.yml

Imperative Recipe Development Workflow

1. Set Up Recipe Class

@Value
@EqualsAndHashCode(callSuper = false)
public class YourRecipe extends Recipe {

    @Option(displayName = "Display Name",
            description = "Clear description.",
            example = "com.example.Type")
    String parameterName;

    @Override
    public String getDisplayName() {
        return "Your recipe name";
    }

    @Override
    public String getDescription() {
        return "What this recipe does.";
    }

    @Override
    public TreeVisitor<?, ExecutionContext> getVisitor() {
        return new YourVisitor();
    }
}

Key Points:

  • Use @Value and @EqualsAndHashCode(callSuper = false) for immutability
  • Ensure all recipes are serializable
  • Define configurable parameters using @Option fields
  • Return a NEW instance from getVisitor() each time (no caching)

2. Implement the Visitor

public class YourVisitor extends JavaIsoVisitor<ExecutionContext> {

    @Override
    public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
        // ALWAYS call super to traverse the tree
        J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx);

        // Check if change is needed (do no harm)
        if (!shouldChange(cd)) {
            return cd;
        }

        // Make changes using JavaTemplate or LST methods
        cd = makeChanges(cd);

        return cd;
    }
}

Visitor Guidelines:

  • Use JavaIsoVisitor when returning the same type (most common)
  • Use JavaVisitor only when changing LST types
  • Call super.visitX() to traverse subtree in most cases. Omit the super call only when certain there could be no further edits below the current LST element
  • Return unchanged LST if no change needed (referential equality check)
  • Treat LSTs as immutable. Use .withX() methods for modifications

3. Use JavaTemplate for Complex Changes

private final JavaTemplate template = JavaTemplate
    .builder("public String hello() { return \"Hello from #{}!\"; }")
    .build();

// In visitor method:
classDecl = template.apply(
    new Cursor(getCursor(), classDecl.getBody()),
    classDecl.getBody().getCoordinates().lastStatement(),
    fullyQualifiedClassName
);

Template Tips:

  • Use #{} for string parameters
  • Use #{any(Type)} for typed LST elements
  • Declare imports: .imports("java.util.List")
  • Add classpath: .javaParser(JavaParser.fromJavaVersion().classpath("library-name"))
  • Prefer context-free templates (default) as they are faster
  • Use .contextSensitive() only when referencing local scope

4. Add Preconditions (Performance)

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
    return Preconditions.check(
        Preconditions.and(
            new UsesType<>("com.example.Type", true),
            new UsesJavaVersion<>(17)
        ),
        new YourVisitor()
    );
}

Benefits: Limits recipe execution to relevant files only, improving performance

Testing Recipes

Test Structure

class YourRecipeTest implements RewriteTest {

    @Override
    public void defaults(RecipeSpec spec) {
        spec.recipe(new YourRecipe("parameter-value"));
    }

    @Test
    void makesExpectedChange() {
        rewriteRun(
            //language=java
            java(
                // Before
                """
                  package com.example;
                  class Before { }
                  """,
                // After
                """
                  package com.example;
                  class After { }
                  """
            )
        );
    }

    @Test
    void doesNotChangeWhenNotNeeded() {
        rewriteRun(
            //language=java
            java(
                """
                  package com.example;
                  class AlreadyCorrect { }
                  """
                // No second argument = no change expected
            )
        );
    }
}

Notice how in Java template strings, the end """ delimiter is one indent to the right of the open delimiter. Java trims everything to the left of that same column.

Testing Best Practices:

  • Test both changes AND no-changes cases
  • Test edge cases
  • Note that the test harness runs multiple cycles to ensure idempotence
  • Add //language=XXX comments to the highest level statement whose string arguments entirely consist of code snippets of that same language. This helps the IDE syntax highlight the test code

ScanningRecipe Pattern

Use when you need to:

  • See all files before making changes
  • Generate new files based on analysis
  • Share data across multiple files
@Value
@EqualsAndHashCode(callSuper = false)
public class YourScanningRecipe extends ScanningRecipe<YourAccumulator> {

    public static class YourAccumulator {
        Map<JavaProject, Boolean> projectData = new HashMap<>();
    }

    @Override
    public YourAccumulator getInitialValue(ExecutionContext ctx) {
        return new YourAccumulator();
    }

    @Override
    public TreeVisitor<?, ExecutionContext> getScanner(YourAccumulator acc) {
        return new JavaIsoVisitor<>() {
            @Override
            public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) {
                // Collect data into accumulator
                return cu;
            }
        };
    }

    @Override
    public TreeVisitor<?, ExecutionContext> getVisitor(YourAccumulator acc) {
        return new JavaIsoVisitor<>() {
            @Override
            public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) {
                // Use data from accumulator to make changes
                return cu;
            }
        };
    }
}

Critical Best Practices

Do No Harm

  • If unsure whether a change is safe, DON'T make it
  • Make minimal, least invasive changes
  • Respect existing formatting

Immutability & Idempotence

  • Recipes must be immutable (no mutable state)
  • Same input → same output, always
  • Use @Value and @EqualsAndHashCode(callSuper = false)
  • getVisitor() must return NEW instance

Never Mutate LSTs

// WRONG
method.getArguments().remove(0);

// CORRECT
method.withArguments(ListUtils.map(method.getArguments(), (i, arg) ->
    i == 0 ? null : arg
));

Naming Conventions

  • Display names: Sentence case, code in backticks, end with period
  • Example: "Change type from OldType to NewType."
  • Recipe names: com.yourorg.VerbNoun (e.g., com.yourorg.ChangePackage)

State Management

  • Within visitor: Use Cursor messaging (getCursor().putMessage())
  • Between visitors: Use ScanningRecipe accumulator
  • Never use ExecutionContext for visitor state

Multi-Module Projects

  • Track per-project data: Map<JavaProject, T>
  • Don't assume single project per repository

Common Patterns

Adding Imports

maybeAddImport("java.util.List");
maybeAddImport("java.util.Collections", "emptyList");

Removing Imports

maybeRemoveImport("old.package.Type");

Chaining Visitors

doAfterVisit(new OtherRecipe().getVisitor());

Checking Types

if (methodInvocation.getType() != null &&
    TypeUtils.isOfClassType(methodInvocation.getType(), "com.example.Type")) {
    // ...
}

Resources

This skill includes several supporting files organized by purpose:

  • Templates (assets/) - Files used as starting points for recipe development:

    • assets/template-imperative-recipe.java - Boilerplate for imperative recipes
    • assets/template-declarative-recipe.yml - YAML recipe template
    • assets/template-recipe-test.java - Test class template

    Load when: Creating a new recipe or needing a template to start from

  • Examples (references/) - Reference documentation loaded as needed:

    • references/example-say-hello-recipe.java - Complete working recipe with test and YAML usage
    • references/example-scanning-recipe.java - Advanced ScanningRecipe pattern for multi-file analysis
    • references/example-declarative-migration.yml - Real-world YAML migration examples

    Load when: Needing to see a complete example, asking "show me an example", or understanding advanced patterns

  • Recipe Catalogs (references/) - Curated lists for finding recipes when building declarative YAML recipes:

    • references/recipes-top.csv - 50 commonly used recipes (best starting point)
    • references/recipes-java-basic.csv - 32 basic Java refactoring operations
    • references/recipes-spring-boot-common.csv - 60 Spring Boot migrations and best practices
    • references/recipes-framework-migrations-common.csv - 16 major framework migrations (10 different frameworks)
    • references/recipes-testing-common.csv - 60 most useful testing recipes
    • references/recipes-dependencies-common.csv - 49 dependency operations (Maven+Gradle when possible)
    • references/recipes-security-common.csv - 30 security vulnerability recipes
    • references/recipes-xml-yaml-json-common.csv - 50 configuration file operations
    • references/recipes-static-analysis-common.csv - 50 code analysis recipes
    • references/recipes-logging.csv - 153 logging framework recipes
    • references/recipes-file-operations.csv - 14 file manipulation recipes
    • Note: references/recipes-all.csv exists for maintenance/script purposes but is too large (4,958 recipes) to be used directly

    Load when: Looking for existing recipes

  • Checklist (references/) - Verification guide:

    • references/checklist-recipe-development.md - Comprehensive verification checklist covering planning, implementation, testing, and distribution

    Load when: Reviewing a recipe for completeness, ensuring best practices, or preparing for distribution

  • Scripts (scripts/) - Utility scripts:

    • scripts/upload-skill.sh - Script to upload/update the skill via API

    Load when: Managing the skill itself (meta-operation)

Quick Reference

Key Classes:

  • Recipe - Base class for all recipes
  • JavaIsoVisitor<ExecutionContext> - Most common visitor
  • JavaTemplate - For generating code snippets
  • RewriteTest - Testing interface
  • ScanningRecipe<T> - Multi-file analysis

Key Methods:

  • getVisitor() - Returns visitor instance
  • super.visitX() - Traverse subtree
  • .withX() - Create modified LST copy
  • ListUtils.map() - Transform lists without mutation
  • doAfterVisit() - Chain additional visitors

Use this skill for help with:

  • Choosing the right recipe type
  • Structuring recipe classes
  • Writing visitor logic
  • Using JavaTemplate
  • Writing tests
  • Debugging common issues
  • Understanding LST structure