allwrite

Recipes

The following list covers all custom recipes provided by allwrite.

Recipe authoring helpers

The allwrite-spi module provides the following public helpers for recipe authors:

Helper Purpose
AllwriteRecipe Base class for regular recipes. Builds display metadata and allwrite tags.
AllwriteScanningRecipe Base class for OpenRewrite scanning recipes with the same metadata support.
RecipeMetadata Builds display name, description, visibility, friendly-name, version, and Dependabot tags.
RecipeVisibility Marks a recipe as INTERNAL or PUBLIC.
ParsingAwareRecipe Restricts the files parsed for a recipe.
ClasspathAwareRecipe Requests additional parser classpath entries and isolated execution.
PostprocessingRecipe Runs additional work after recipe changes are applied.
PostprocessingResult Reports postprocessing success or a failure with an error message.

See Writing recipes for usage guidelines and examples.

YAML

pl.allegro.tech.allwrite.recipes.yaml.ExpandMappings { data-toc-label=”ExpandMappings” }

Transforms flat/collapsed YAML properties into a hierarchical structure and merges duplicate paths.

Options:

Name Type Required Description
prefix String No Only transform entries matching this prefix. Must match key parts exactly (e.g. myapp will not match myapplication).
excludes List<String> No Do not transform entries matching these prefixes.

Before:

myapp.metrics.graphite.enabled: true
myapp.metrics.graphite:
  host: localhost
  port: 2003
myapp:
  i18n:
    enabled: true

After:

myapp:
  metrics:
    graphite:
      enabled: true
      host: localhost
      port: 2003
  i18n:
    enabled: true

pl.allegro.tech.allwrite.recipes.yaml.UnnestProperties { data-toc-label=”UnnestProperties” }

Removes one level of nesting from a YAML mapping at a specified path, moving child entries up to the parent level.

Options:

Name Type Required Description
targetPath String Yes Dot-separated path to the mapping entry to unnest.

Before:

spring:
  groovy:
    template:
      configuration:
        auto-indent: true
        auto-new-line: true

After (with targetPath = spring.groovy.template.configuration):

spring:
  groovy:
    template:
      auto-indent: true
      auto-new-line: true

pl.allegro.tech.allwrite.recipes.yaml.AddTopLevelLineBreaks { data-toc-label=”AddTopLevelLineBreaks” }

Ensures top-level YAML mapping entries are separated by blank lines for conventional formatting.

Before:

server:
  port: 8080
spring:
  application:
    name: my-app
management:
  endpoints:
    enabled: true

After:

server:
  port: 8080

spring:
  application:
    name: my-app

management:
  endpoints:
    enabled: true

pl.allegro.tech.allwrite.recipes.yaml.DeleteProperty { data-toc-label=”DeleteProperty” }

Enhanced version of OpenRewrite’s DeleteProperty that:

Options:

Name Type Required Description
propertyKey String Yes The key to be deleted.
coalesce Boolean No Simplify nested map hierarchies into their simplest dot separated property form.
relaxedBinding Boolean No Whether to match the propertyKey using relaxed binding rules. Defaults to true. If you want to use exact matching in your search, set this to false.
filePattern String No A glob expression representing a file path to search for (relative to the project root). Blank/null matches all.

Before:

server.port: 8080 # comment should stay
management.server.port: 8081 # comment should disappear
smth-else: 1

After (with propertyKey = management.server.port):

server.port: 8080 # comment should stay
smth-else: 1

pl.allegro.tech.allwrite.recipes.yaml.FindKey { data-toc-label=”FindKey” }

A lighter and faster alternative to OpenRewrite’s FindKey. Searches for a YAML key using simple case-insensitive matching instead of JsonPath. Intended for use as a precondition that fires when a YAML document contains a specified key.

Options:

Name Type Required Description
key String No The YAML key to search for (case-insensitive, dot-separated path).

pl.allegro.tech.allwrite.recipes.yaml.YamlEntryHasValue { data-toc-label=”YamlEntryHasValue” }

Precondition recipe that matches YAML entries at a given JSON path having a specific value.

Options:

Name Type Required Description
key String Yes JSON path expression to locate the entry.
expectedValue String Yes The value the entry must have to match.

Gradle

pl.allegro.tech.allwrite.recipes.gradle.AddGradleDependency { data-toc-label=”AddGradleDependency” }

A two-pass scanning recipe that adds a dependency to a Gradle project. In the scan phase, it parses the TOML version catalog (gradle/libs.versions.toml) and discovers module roots. In the transform phase, it adds the library entry to the version catalog and a dependency reference in build.gradle(.kts).

Options:

Name Type Required Description
configuration String Yes Gradle configuration (e.g. implementation, testImplementation).
groupId String Yes Dependency group ID.
artifactId String Yes Dependency artifact ID.
version String No Dependency version. Used when no version catalog is present.
versionCatalogName String No Alias for the dependency in the version catalog. Auto-generated from coordinates if not specified.

Before (with configuration = "testRuntimeOnly", groupId = "org.junit.platform", artifactId = "junit-platform-launcher"):

gradle/libs.versions.toml:

[libraries]
mylib-starter = { group = "com.example.lib", name = "mylib-starter" }

build.gradle.kts:

dependencies {
    implementation(libs.test)
}

After:

gradle/libs.versions.toml:

[libraries]
mylib-starter = { group = "com.example.lib", name = "mylib-starter" }
junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher" }

build.gradle.kts:

dependencies {
    implementation(libs.test)
    testRuntimeOnly(libs.junit.platform.launcher)
}

pl.allegro.tech.allwrite.recipes.gradle.ChangeGradleDependency { data-toc-label=”ChangeGradleDependency” }

Changes a Gradle dependency’s group ID, artifact ID, and optional version. It supports build.gradle, build.gradle.kts, and gradle/libs.versions.toml, updating matching version-catalog entries and refs when present. The old group and artifact IDs accept glob patterns, so * can match any coordinate segment.

Options:

Name Type Required Description
oldGroupId String Yes Dependency group ID to replace. Accepts glob patterns.
oldArtifactId String Yes Dependency artifact ID to replace. Accepts glob patterns.
newGroupId String No Replacement group ID. Defaults to the old group ID.
newArtifactId String No Replacement artifact ID. Defaults to the old artifact ID.
newVersion String No Replacement version. If omitted, the version is removed.

Before (with oldGroupId = "com.fasterxml.jackson.module", oldArtifactId = "jackson-module-afterburner"):

build.gradle.kts:

dependencies {
    implementation("com.fasterxml.jackson.module:jackson-module-afterburner:2.17.2")
}

After:

build.gradle.kts:

dependencies {
    implementation("tools.jackson.module:jackson-module-blackbird:3.1.4")
}

pl.allegro.tech.allwrite.recipes.gradle.UpdateGradleDependency { data-toc-label=”UpdateGradleDependency” }

Updates dependency versions using regular expressions. Converts build files to plain text and applies regex-based replacements. Supports dependency declarations in multiple formats:

Options:

Name Type Required Description
groupId String Yes Dependency group ID to match.
artifactId String Yes Dependency artifact ID to match.
targetVersion String Yes The new version to set.
sourceVersionPattern String No Regex pattern for the current version. Defaults to \d+.\d+.\d+.
filePatterns List<String> No Glob patterns for files to scan. Defaults to *.gradle, *.gradle.kts, and gradle/*.toml.

Before (with groupId = "com.example", artifactId = "some-dependency", targetVersion = "2.0.0"):

classpath("com.example:some-dependency:1.0.0")

After:

classpath("com.example:some-dependency:2.0.0")

Java / Kotlin

pl.allegro.tech.allwrite.recipes.java.ChangeType { data-toc-label=”ChangeType” }

Enhanced version of OpenRewrite’s ChangeType with additional support for renaming variables.

Options:

Name Type Required Description
oldFullyQualifiedTypeName String Yes Fully-qualified class name of the original type.
newFullyQualifiedTypeName String Yes Fully-qualified class name of the replacement type, or the name of a primitive such as “int”. The OuterClassName$NestedClassName naming convention should be used for nested classes.
ignoreDefinition Boolean No When set to true the definition of the old type will be left untouched. This is useful when you’re replacing usage of a class but don’t want to rename it.

Before:

public class OldType {}

public class Main {
    static void main() {
        OldType oldType = new OldType();
    }
}

After:

public class NewType {}

public class Main {
    static void main() {
        // the vanilla OpenRewrite would keep 'oldType' variable name
        NewType newType = new NewType();
    }
}

pl.allegro.tech.allwrite.recipes.java.ChangeRecordField { data-toc-label=”ChangeRecordField” }

Renames a field on a Java record type across all usages, including field access expressions and accessor method invocations.

Options:

Name Type Required Description
declaringTypeFqn String Yes Fully qualified name of the record type.
oldFieldName String Yes Current field name.
newFieldName String Yes New field name.

Given third-party class:

public record SomeRecord(String oldName) { }

then has been changed to:

public record SomeRecord(String newName) { }

In Java

Before (with declaringTypeFqn = "com.example.SomeRecord", oldFieldName = "oldName", newFieldName = "newName"):

class Foo {
    String foo(SomeRecord someRecord) {
        return someRecord.oldName();
    }
}

After:

class Foo {
    String foo(SomeRecord someRecord) {
        return someRecord.newName();
    }
}

In Kotlin

Before (with declaringTypeFqn = "com.example.SomeRecord", oldFieldName = "oldName", newFieldName = "newName"):

fun foo(someRecord: SomeRecord): String {
    return someRecord.oldName
}

After:

fun foo(someRecord: SomeRecord): String {
    return someRecord.newName
}

pl.allegro.tech.allwrite.recipes.java.SimplifyMethodChain { data-toc-label=”SimplifyMethodChain” }

Enhanced version of OpenRewrite’s SimplifyMethodChain with support for Kotlin.

Options:

Name Type Required Description
methodPatternChain List<String> Yes A list of method patterns that are called in sequence.
newMethodName String Yes The method name that will replace the existing name. The new method name target is assumed to have the same arguments as the last method in the chain.
matchOverrides Boolean No When enabled, find methods that are overrides of the method pattern.

Before:

fun getAuthorName(book: Book) = book.author.name

After:

fun getAuthorName(book: Book) = book.authorName

pl.allegro.tech.allwrite.recipes.java.RemoveUnusedPrivateFields { data-toc-label=”RemoveUnusedPrivateFields” }

Enhanced version of OpenRewrite’s RemoveUnusedPrivateFields with additional onlyRemoveFieldsOfType parameter.

Options:

Name Type Required Description
onlyRemoveFieldsOfType String[] No Array of fully-qualified class names

Before:

class Example {
    private com.example.Foo unusedFoo;
    private com.example.Bar unusedBar;
}

After (with onlyRemoveFieldsOfType = [com.example.Foo])

class Example {
    private com.example.Bar unusedBar;
}

pl.allegro.tech.allwrite.recipes.java.RemoveUnusedImportsOfType { data-toc-label=”RemoveUnusedImportsOfType” }

Removes unused imports, but only of given type.

Options:

Name Type Required Description
types String[] No Array of fully-qualified class names

Before:

import com.example.Foo; // unused
import com.example.Bar; // unused

After (with types = [com.example.Foo]):

import com.example.Bar; // unused

pl.allegro.tech.allwrite.recipes.java.ReplaceFactoryWithConstructor { data-toc-label=”ReplaceFactoryWithConstructor” }

Replaces factory method invocations with direct constructor calls. Handles both new Factory().create(args) and identifier-based factory invocations. Automatically manages import changes.

Options:

Name Type Required Description
fullyQualifiedTypeName String Yes Fully qualified name of the target type to construct.
factoryClassNamePattern String Yes Regex pattern matching the factory class name.

Before (with fullyQualifiedTypeName = "com.example.MyClass", factoryClassNamePattern = "MyClassFactory"):

import com.example.MyClassFactory;

class Foo {
    void bar() {
        MyClass obj = new MyClassFactory().create(arg1, arg2);
    }
}

After:

import com.example.MyClass;

class Foo {
    void bar() {
        MyClass obj = new MyClass(arg1, arg2);
    }
}

Spring

pl.allegro.tech.allwrite.recipes.spring.FindSpringProperty { data-toc-label=”FindSpringProperty” }

Searches for a Spring property by key across application*.properties, application*.yml, and application*.yaml files. Supports relaxed binding and glob patterns for both key matching and profile filtering via file name suffix.

Options:

Name Type Required Description
propertyKey String Yes The property key to search for. Compared using relaxed binding, supports glob.
expectedValue String Yes The property value to match. If null, matches any value.
fileNameSuffix String No Glob pattern for file name suffix, used to filter by Spring profile (e.g. -integration).

pl.allegro.tech.allwrite.recipes.spring.DeleteSpringProperty { data-toc-label=”DeleteSpringProperty” }

Deletes a Spring property by key from both YAML and .properties files.

Options:

Name Type Required Description
propertyKey String Yes The property key to delete.

Before (with propertyKey = "myapp.isolated-environment"):

myapp:
  isolated-environment:
    nested-object:
      scalar: 123
      list:
      - a
      - b
  test:
    123

After:

myapp:
  test:
    123

pl.allegro.tech.allwrite.recipes.spring.DeleteSpringPropertyWithValue { data-toc-label=”DeleteSpringPropertyWithValue” }

Deletes a Spring property only if it has a specific value. Works across both YAML and .properties files.

Options:

Name Type Required Description
propertyKey String Yes The property key to delete.
propertyValue String Yes The exact value the property must have to be deleted.

Before (with propertyKey = "management.endpoint.configprops", propertyValue = "true"):

management:
  endpoint:
    configprops: true
    health:
      enabled: true

After:

management:
  endpoint:
    health:
      enabled: true

pl.allegro.tech.allwrite.recipes.spring.DeleteSpringPropertyFromSpringAnnotations { data-toc-label=”DeleteSpringPropertyFromSpringAnnotations” }

Removes property entries from @SpringBootTest(properties = ...) and @TestPropertySource(properties = ...) annotations in Java source code. Supports glob matching on property names with relaxed binding.

Options:

Name Type Required Description
propertyName String Yes The property key to remove. Supports glob (e.g. management.metrics.binders.*.enabled).

Before (with propertyName = "myapp.test"):

import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest(properties = {
  "server.port=8080",
  "myapp.test=1",
  "myapp.best=2"
})
class Example {}

After:

import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest(properties = {
  "server.port=8080",
  "myapp.best=2"
})
class Example {}

pl.allegro.tech.allwrite.recipes.spring.ChangeSpringPropertyKey { data-toc-label=”ChangeSpringPropertyKey” }

Renames a Spring property key across YAML, .properties, and other files (e.g. Markdown). For YAML and .properties, delegates to OpenRewrite’s ChangeSpringPropertyKey. For other files, performs a regex find-and-replace supporting both lower-hyphen and lowerCamel case formats.

Options:

Name Type Required Description
oldKey String Yes The property key to rename. Supports glob.
newKey String Yes The new name for the property key.

Before (with oldKey = "i18n.language-bundle.enabled", newKey = "myapp.i18n.language-bundle.enabled"):

i18n:
  language-bundle:
    enabled: true

After:

myapp.i18n.language-bundle.enabled: true

pl.allegro.tech.allwrite.recipes.spring.RenameTaskExecutorBean { data-toc-label=”RenameTaskExecutorBean” }

Adds @Qualifier("applicationTaskExecutor") to TaskExecutor injection points (constructor parameters, @Bean method parameters, and @Autowired fields) to align with the Spring Boot 3.5 bean naming change. Skips projects that define their own custom taskExecutor bean.

Before:

import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;

@Component
public class MyService {
    public MyService(TaskExecutor taskExecutor) {}
}

After:

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;

@Component
public class MyService {
    public MyService(@Qualifier("applicationTaskExecutor") TaskExecutor taskExecutor) {}
}

pl.allegro.tech.allwrite.recipes.spring.AddNonNullableTypeBoundsToSpringRepositories { data-toc-label=”AddNonNullableTypeBoundsToSpringRepositories” }

Adds : Any upper bounds to type parameters of Kotlin classes/interfaces extending Spring Data repository interfaces, as required by Spring Framework 7 / Spring Boot 4 JSpecify nullability annotations. Only applies to Kotlin source files. This recipe is automatically included in the Spring Boot 4.0 migration.

Before:

import org.springframework.data.repository.CrudRepository

interface UserRepository<T, ID> : CrudRepository<T, ID>

After:

import org.springframework.data.repository.CrudRepository

interface UserRepository<T : Any, ID : Any> : CrudRepository<T, ID>

pl.allegro.tech.allwrite.recipes.spring.ReplaceStatusCodeValue { data-toc-label=”ReplaceStatusCodeValue” }

Replaces deprecated ResponseEntity.getStatusCodeValue() / .statusCodeValue with getStatusCode().value() / .statusCode.value() as required by Spring Framework 7 / Spring Boot 4. Works across Java, Groovy, and Kotlin source files. Included in the SpringBoot4_0 upgrade recipe.

Before (Java):

import org.springframework.http.ResponseEntity;

class Example {
    void test() {
        ResponseEntity<String> response = ResponseEntity.ok("hello");
        int status = response.getStatusCodeValue();
    }
}

After (Java):

import org.springframework.http.ResponseEntity;

class Example {
    void test() {
        ResponseEntity<String> response = ResponseEntity.ok("hello");
        int status = response.getStatusCode().value();
    }
}

Before (Kotlin/Groovy):

import org.springframework.http.ResponseEntity

class Example {
    fun test() {
        val response: ResponseEntity<String> = ResponseEntity.ok("hello")
        val status: Int = response.statusCodeValue
    }
}

After (Kotlin/Groovy):

import org.springframework.http.ResponseEntity

class Example {
    fun test() {
        val response: ResponseEntity<String> = ResponseEntity.ok("hello")
        val status: Int = response.statusCode.value()
    }
}