Skip to content

Recipe quality triage: fixes, wiring and polish (A1–L3) #1330

Open

Description

@timtebeek

We triaged all recipes in this repo against our recipe review checklist (the one used for openrewrite/rewrite-spring#1201). That covers 205 Java recipe classes and 292 declarative recipes in 52 YAML files. This issue tracks what we found, grouped so each item can be picked up independently.

Each item is self-contained, so you can pick one up on its own or point an agent at it ("work on item F1 of this issue"). Please reference the item ID in your PR title or description. As soon as the PR is opened, tick the item's box and add the PR number after its title, e.g. (#1234), so nobody else picks it up. If the PR only covers some bullets, also add "Fixed in #1234." to those bullets. Untick the box if the PR is closed without merging. Where an item overlaps an existing issue, that issue is linked.

How to pick up an item
  • Line numbers are against main at ac6f4827 and may have drifted, so search by symbol if needed.
  • Paths are abbreviated:
    • M/ = src/main/java/org/openrewrite/java/migrate/
    • T/ = src/test/java/org/openrewrite/java/migrate/
    • Y/ = src/main/resources/META-INF/rewrite/
  • Confirmed means someone traced the code path and the behaviour is certain; no probe test has been run yet. Suspected means it was inferred from reading. Write the probe test first; if it does not reproduce, close the item with a note.
  • Prefer bailing out (making no change) over guessing when a recipe can't preserve semantics.
  • Every fix comes with a test. Tests use realistic classpaths and avoid TypeValidation.none(). Expected output must be correct, not just a record of today's behaviour.
  • Use one concern per commit, with an imperative headline that names APIs in backticks.
  • Items in A need a maintainer decision first. Comment on the issue before starting.
  • When you open a PR, tick the item and link the PR right away (see above).
Start here: the most harmful confirmed items

These change behaviour silently or break the build, and are reached through the main upgrade composites:

  1. G1: HttpSessionInvalidate replaces session.invalidate() with request.logout() in every JavaxMigrationToJakarta run.
  2. B1: AddLombokMapstructBinding creates annotationProcessorPaths without Lombok in UpgradeToJava17.
  3. F1: UseLombokSetter deletes setters that transform their argument.
  4. E1 and D1: Set.of/Map.of throw on duplicates and nulls where the replaced code didn't.
  5. C1–C3: the Java 21 switch restyles drop branches or change control flow.
  6. B3: Docker tags become ones that don't exist.
  7. G7: Faces 3 text replacements stop the Faces 4 XML updates from running.
  8. C5: ThreadStopUnsupported leaves unreachable code.

A. Wiring & placement (decide first)

  • A1 Move opinionated restyles out of the mandatory version upgrades

    Details
    • Problem: these change style or semantics in code that compiles fine on the new JDK. Each also has its own bugs (see C/D).
      • UpgradeToJava21 (Y/java-version-21.yml:44-47): NullCheckAsSwitchCase, IfElseIfConstructToSwitch, SwitchCaseAssignmentsToSwitchExpression, SwitchCaseReturnsToSwitchExpression.
      • UpgradeToJava25: ReplaceSystemOutWithIOPrint (Y/java-version-25.yml:36). MigrateMainMethodToInstanceMain was deliberately kept in JavaBestPractices only (Y/java-best-practices.yml:34-36).
      • Java8toJava11 → UpdateLombokToJava11 (Y/lombok.yml:79-83): rewrites val to final var, and writes lombok.val.flagUsage = error into every lombok.config, although val still works on Lombok 1.18.
    • Fix: move them to JavaBestPractices (or a Java21BestPractices), and keep only what the JDK upgrade requires.
  • A2 Vendor switches and scope changes reached by every Jakarta EE / Java 11+ migration

    Details
    • Provider switches: JavaxMigrationToJakarta (Y/jakarta-ee-9.yml:32) includes javaee8 → javaee7 → javaee6, and those switch providers:

      • OpenJPA → EclipseLink in persistence.xml (Y/java-ee-7.yml:29,35).
      • Apache BVal → Hibernate Validator in validation.xml (Y/java-ee-8.yml:31,61). ApacheDefaultProvider's display name says "Flags", but it rewrites.

      That is right on WebSphere Liberty and wrong on TomEE. The same chain brings JpaCacheProperties (G5) and HttpSessionInvalidate (G1).

    • IBMSemeru (Y/ibm-java.yml:33-34 → :133-180): includes RemovedJavaXMLWSModuleProvided and RemovedJaxBModuleProvided, whose own display names say "in WebSphere Liberty applications". They set the JAXB/JAX-WS API to provided, so a non-Liberty app loses JAXB at runtime. SUSPECTED.

    • AddCommonAnnotationsDependencies (Y/add-common-annotations-dependencies.yml:42-45): sets jakarta.annotation-api to provided unconditionally in every Java 11+ upgrade (Maven only). A shaded Java SE app loses the API at runtime. SUSPECTED.

    • Fix: move the vendor and Liberty specifics into the IBM/OpenJPA composites, or gate them on the provider or on war/ear packaging.

  • A3 JavaBestPractices → JavaUtilAPIs re-enables recipes the Java 11 chain disabled

    Details
    • Where: Y/java-best-practices.yml:64.
    • Problem: JavaUtilAPIs brings back the null-unsafe collection factories that were left out of the Java 11 chain for null safety (Collections.singleton and Collections.singletonList allow null, Set.of and List.of not #250); see D1/D2. It also brings UseEnumSetOf ("too opinionated", UseEnumSetOf is too opinionated for Spring Boot / Java upgrades #958) with convertEmptySet at its default true, and re-runs 10 Java recipes already in the UpgradeToJava25 chain (Java recipes are not Singleton-guarded).
    • Conflict: RemoveFinalizerFromZip (Y/java-util-apis.yml:39) deletes the zip finalize() calls, while RemovedZipFinalizeMethods (Y/java-version-17.yml:206-228) renames them to end()/close(). Pick one.
    • Fix: list only the JavaUtilAPIs entries that aren't already in the chain, and decide on the null-unsafe ones.
  • A4 Wire, document as opt-in, or delete unreachable recipes

    Details

    24 of 205 Java recipes are referenced by no composite.

    • Wire:
      • io.AddInputStreamBulkReadMethod, lang.StringRulesRecipes and lang.UseStringIsEmptyRecipe → JavaBestPractices.
      • search.FindLocaleDateTimeFormats → UpgradeToJava21, as Java 25 does with FindCompletableFutureDefaultExecutor.
      • lombok.AdoptLombokGetterMethodNames/AdoptLombokSetterMethodNames → LombokBestPractices, but only after F3.
    • Document as opt-in: AddSurefireFailsafeArgLine (after B6), LombokValueToRecord, AddStopBubblingToLombokConfig, ConsolidateLombokConfig, search.* (FindJavaVersion, PlanJavaMigration, AboutJavaVersion, FindInternalJavaxApis, FindDtoOverfetching, FindDataUsedOnDto), lang.FindThreadStartInConstructor, UseTabsOrSpaces (arguably rewrite-static-analysis).
    • Delete or rewrite (each is broken; see the referenced items): RemovedSubjectMethods (D9), DontOverfetchDto (H2), AddScopeToInjectedClass (G9), JacksonProviderOverrides (G9), metrics.SimplifyMicrometerMeterTags (D10, or move it to rewrite-micrometer). Also:
      • AddJDeprScanPlugin: @Incubating, pins 3.0.0-alpha-1, untested, and adds the plugin to every pom despite its javadoc.
      • AddSuppressionForIllegalReflectionWarningsPlugin: configures maven-jar-plugin on war/ear, where it doesn't run. It is a no-op with no test.
    • Duplicate: io.ReplaceFileInOrOutputStreamFinalizeWithClose duplicates YAML RemovedFileIOFinalizeMethods (Y/java-version-17.yml:290-307). Delete one.
    • Unreferenced YAML building blocks: ComIntelliJAnnotationsToOrgJetbrainsAnnotations (add to MigrateToJSpecify before MigrateFromJetbrainsAnnotations), WasDevMvnChangeParentArtifactId, AddJaxbDependenciesWithoutRuntime.
  • A5 Remove duplicate and dead entries (Stop running the same recipe twice in the Java 8, 11, 21 and Faces 4.1 migrations #1340, Stop half-migrating javax.servlet in RemovalsServletJakarta10 and add Jakarta APIs for subpackage-only users #1343)

    Details
  • A6 Placement questions

    Details
    • UpgradeToJava6 bumps JaCoCo (Y/java-version-6.yml:29). Move it to UpgradePluginsForJava11+.
    • UpgradeToJava21 calls SetupJavaUpgradeJavaVersion with no option (Y/java-version-21.yml:40), so it relies on the upstream default being 21. Move it to UpgradePluginsForJava21 with minimumJavaMajorVersion: 21.
    • MigrateFastjsonForJakarta10 (Y/jakarta-ee-10.yml:56,812) is a fastjson1 → 2 library migration inside JakartaEE10 (see also G8).
    • UpgradeJavaForJetty12 (Y/jakarta-ee-9.yml:1679-1686) bumps the build to Java 17 inside an EE 9 migration, without the UpgradeToJava17 source fixes.
    • JakartaEE11 (Y/jakarta-ee-11.yml:19-58) leaves servlet, persistence, cdi, validation, el, ws.rs and websocket on EE 10 versions, and doesn't ensure Java 17. Its description says it "flags", but it doesn't.
    • Library bumps in Java composites have no stated JDK reason: Y/java-version-17.yml:55-81 (guice, commons-codec, spotbugs, mapstruct) and Y/java-version-21.yml:82-94 (guava 29.0, byte-buddy, modelmapper).
    • EnableLombokAnnotationProcessor is defined in Y/java-version-25.yml:172 but first used in UpgradeToJava17. Move it to java-version-17.yml or lombok.yml.
    • UpdateAnnotationAttributeJavaxToJakarta (Y/jakarta-ee-10.yml:39-44) fixes "javax.jms.Queue" string attributes that go stale at EE 9. Move it to JavaxMigrationToJakarta.
    • The Kotlin floor UpgradeJavaVersion(24) (Y/java-version-25.yml:315-334) also moves Dockerfiles, CI images and .sdkmanrc to the EOL non-LTS 24. Give the floor a build-only variant. SUSPECTED.

B. Build files, dependencies & multi-module

  • B1 AddLombokMapstructBinding creates annotationProcessorPaths without Lombok (Keep Lombok running when AddLombokMapstructBinding creates annotationProcessorPaths #1335)

    Details
  • B2 UpgradeKotlinJvmTargetVersion: targets beyond the Kotlin compiler, and missed shapes

    Details
    • Where: M/UpgradeKotlinJvmTargetVersion.java:106-110,139-148,248-276, reached through the Kotlin-capped UpgradeJavaVersion(24) in Y/java-version-25.yml:315-334.
    • Too-high target (CONFIRMED path): Kotlin 1.9 supports at most jvmTarget 21, and 2.0/2.1 are also below 24. Maven kotlinc fails with "Unknown JVM target version", and Gradle KTS can't resolve JvmTarget.JVM_24. Fix: cap at the resolved Kotlin version's maximum (1.9→21, 2.0→22, 2.1→23, 2.2→24).
    • Missed shapes (SUSPECTED): kotlinOptions.jvmTarget = '11', compilerOptions.jvmTarget.set(..), Groovy compilerOptions { }, JvmTarget.fromTarget("11"), <kotlin.compiler.jvmTarget>, and <jvmTarget> under <executions>. When the Java target moves but Kotlin's doesn't, KGP fails with "Inconsistent JVM-target compatibility".
    • Also: the Groovy/Kotlin visitors run over every .groovy/.kt source, not just build scripts (:72-89).
  • B3 UpgradeDockerImageVersion produces tags that don't exist (Drop pinned patch versions when upgrading Docker image tags (B3 of #1330) #1352)

    Details
    • Where: M/UpgradeDockerImageVersion.java:56,207-217.
    • Problem (CONFIRMED): VERSIONED_TAG (\d{1,3})(\D.*)? keeps the patch as a suffix. eclipse-temurin:17.0.9_9-jdk → 25.0.9_9-jdk, openjdk:8u342-jre → eclipse-temurin:25u342-jre, amazoncorretto:17.0.9 → 25.0.9.
    • Fix: drop the patch/update part, or skip pinned patch versions.
    • Also (SUSPECTED, :186-193): the switch from openjdk to eclipse-temurin keeps OpenJDK-only variants (-slim, -slim-bullseye, -oraclelinux8, -buster), which Temurin doesn't publish.
  • B4 AddJaxbRuntime: swaps runtimes in projects that don't use JAXB, possibly downgrading Jakarta JAXB 4

    Details
    • Where: M/javax/AddJaxbRuntime.java:186-206,233-247, reached via Y/jaxb-apis.yml:37 from Java8toJava11, and so from every Java 17/21/25 upgrade.
    • Problem (CONFIRMED for the unconditional swap): the ChangeDependency delegates run on every build file, even when usesJaxb is false, so com.sun.xml.bind:jaxb-impl becomes org.glassfish.jaxb:jaxb-runtime:2.3.x. SUSPECTED: ChangeDependencyGroupIdAndArtifactId has no downgrade guard, so a Jakarta jaxb-impl:4.0.x ends up on the javax 2.3 runtime.
    • Also:
      • usesJaxb is one repo-wide flag (:77,113); key it by project.
      • The description (:66-70) names compileOnly/provided, but the code adds runtimeOnly/runtime.
    • Probe: a pom with com.sun.xml.bind:jaxb-impl:4.0.5 and jakarta.xml.bind-api:4.0.2, run through Java8toJava11.
    • Related: AddJaxbAPIDependencies silently no-ops when javax.xml.bind is only JDK-provided #1246 and AddJaxwsRuntime blindly injects com.sun.xml.ws:jaxws-rt into projects using Apache CXF #1314.
  • B5 Repo-wide flags in scanning recipes

    Details
    • M/jakarta/HasJettyDependency.java:46-67,87 (SUSPECTED): one global AtomicBoolean. One module with an embedded Jetty 9 test server moves all modules to Java 17 and every org.eclipse.jetty:* to 12.0.x (Y/jakarta-ee-9.yml:1667-1685).
    • M/search/ModuleHasKotlinSource.java:72-93 with Y/java-version-25.yml:359-366 (SUSPECTED): a parent pom has no .kt files, so invertMarking lets it move to release 25, and Kotlin 1.x children inherit that. Probe: a parent with maven.compiler.release=17 and a Kotlin 1.9 child.
    • M/jakarta/HasNoJakartaAnnotations.java:51-65 with Y/jakarta-ee-9.yml:1552-1564 (SUSPECTED): it scans before the javax → jakarta rename, so a javax.annotation.PostConstruct user counts as having "no Jakarta annotations", and jakarta.annotation-api is removed on Boot 2.x.
    • See also B1 (mapstructVersion) and B4 (usesJaxb).
  • B6 AddSurefireFailsafeArgLine adds @{argLine} without defining argLine

    Details
    • Where: M/AddSurefireFailsafeArgLine.java:102-104,139-150 (unreachable; see A4).
    • Problem (CONFIRMED): without JaCoCo, Surefire passes the literal @{argLine}, and the forked JVM fails with "Could not find or load main class @{argLine}". The javadoc at :120-121 claims otherwise.
    • Fix: reuse AddMockitoJavaAgentToMavenSurefirePlugin:92-100,137-139, which handles exactly this case.
  • B7 Lombok dependency handling

    Details
  • B8 onlyIfUsing globs (Add jspecify dependency when migrating Micrometer io.micrometer.core.lang annotations #1338, Stop half-migrating javax.servlet in RemovalsServletJakarta10 and add Jakarta APIs for subpackage-only users #1343)

    Details
  • B9 Maven-only recipes in shared composites

    Details
    • AddMockitoJavaAgentToMavenSurefirePlugin (Y/java-version-25.yml:258): Gradle projects using Mockito get nothing under Java 25.

    • CommentJava24KotlinCap (M/CommentJava24KotlinCap.java:68): Gradle Kotlin modules capped at 24 get no explanation.

    • AddJakartaXmlBindRuntime, AddApiDependencyForImports and RetainJaxbApiForArquillianRecorder (Y/jakarta-ee-9.yml:1019-1051): Gradle projects never get a Jakarta JAXB runtime.

    • M/jakarta/DoesNotHaveProvidedApiDependency.java:53-70 (SUSPECTED):

      • Maven: misses provided inherited from the parent's dependencyManagement.
      • Gradle: checks only compileOnly, not providedCompile.

      Either way, a compile-scope API gets added to WARs.

    • M/javax/AddJaxwsRuntime.java:103-108: the Gradle runtime goes into compileOnly + testImplementation, so standalone apps lack it at runtime (documented). See also AddJaxwsRuntime blindly injects com.sun.xml.ws:jaxws-rt into projects using Apache CXF #1314, and Investigate how to migrate CXF projects to Java 11 #28 for CXF projects, where a JAX-WS runtime shouldn't be added at all.

  • B10 MigrateGraalVMResourceConfig loses resources

    Details
    • Where: M/MigrateGraalVMResourceConfig.java, in UpgradeToJava25 (Y/java-version-25.yml:45).
    • Quoted patterns (CONFIRMED, :320-381): \Q...\E, which native-image-agent emits for every literal resource, becomes a glob that matches nothing.
    • Excludes (CONFIRMED, :115,139-157): excludes are always dropped. When some includes can't be converted, the file ends up half old format and half new.
    • Target format (SUSPECTED): writing "resources": [ {"glob": ..} ] into resource-config.json may not be what GraalVM's legacy parser accepts there. Check LegacyResourceConfigurationParser before fixing the rest.
  • B11 Smaller build-file items

    Details
    • M/UpgradeUnmodeledMavenJavaVersion.java:46-52 (SUSPECTED): bumps fixture poms under src/test/resources, src/it and archetype-resources.
    • M/UpdateSdkMan.java:92-94,134 (SUSPECTED): only the root .sdkmanrc is handled, and a commented #java= line can match.
    • M/UpgradeJavaCiContainerImage.java:37: the display name says CircleCI only, but it also covers GitHub Actions.

C. Java language migrations (lang, lang/var)

  • C1 IfElseIfConstructToSwitch: wrong case null, break retargeted, dominated labels (Leave if-else-if chains alone when a switch would change behaviour or not compile #1336)

    Details
  • C2 NullCheckAsSwitchCase drops the else branch (Do not drop the else branch when merging a null check into a switch #1331)

    Details
    • Where: M/lang/NullCheckAsSwitchCase.java:69-98 and M/lang/NullCheck.java:43-46, in UpgradeToJava21.
    • Problem (CONFIRMED): if (s == null) { a(); } else { b(); } switch (s) {..} removes the if and keeps only a() as case null, so b() is gone. Fix: require whenNotNull() == null.
    • Design question: the recipe only fires when the null branch falls through into the switch, which today throws an NPE. The rewrite turns that NPE into normal completion, while the truly equivalent shape (a null branch ending in return/throw) is rejected. Consider inverting the rule.
  • C3 SwitchCaseAssignmentsToSwitchExpression: labeled break, selector reads

    Details
    • Where: M/lang/SwitchCaseAssignmentsToSwitchExpression.java, in UpgradeToJava21.
    • Labeled break (CONFIRMED, :176): x = 5; break outer; becomes yield 5, so the loop is no longer left. Require getLabel() == null.
    • Selector reads the variable (CONFIRMED, :71-87,93-170): String s = "a"; switch (s) { case "a": s = "b"; ..} becomes String s = switch (s) {..}, which fails with "might not have been initialized". With int x = i; switch (i++), the default branch reads the incremented i.
    • Comments (SUSPECTED, :111-119): comments in arrow blocks and in the break prefix are lost.
  • C4 Other switch recipes

    Details
    • SwitchCaseEnumGuardToLabel (CONFIRMED, :109-114): any identifier becomes a label, so case Suit s when s == fav -> becomes case fav ->. Require an enum-constant field type.
    • RefineSwitchCases (CONFIRMED, :135-150): a non-block then/else part (return;, break;, a nested if) becomes an arrow body, which is illegal. SUSPECTED (:72-75): a reassigned pattern variable can't be used in a guard.
    • SwitchCaseReturnsToSwitchExpression (SUSPECTED, :58-61, test :336-372):
      • For an exhaustive enum switch without default, the following fallback return/throw is deleted, so new constants now throw MatchException.
      • return; cases yield an invalid return switch.
  • C5 ThreadStopUnsupported replaces the call with a throw anywhere

    Details
    • Where: M/lang/ThreadStopUnsupported.java:52-55.
    • Problem (CONFIRMED): on Java 21+, t.stop() becomes throw new UnsupportedOperationException() wherever it appears. Any following statement becomes unreachable (a compile error), and () -> t.stop() becomes () -> throw .., a syntax error. The tests only use the call as the last statement.
    • Fix: only replace a block's last statement, and otherwise just add the comment.
    • Also:
      • There's no UsesMethod precondition, and UsesJavaVersion runs again for every match.
      • The comment is a hand-built TextComment; use the Comments trait (K5).
  • C6 MigrateMainMethodToInstanceMain on interfaces, abstract classes, enums and records

    Details
    • Where: M/lang/MigrateMainMethodToInstanceMain.java:90-151.
    • Problem (CONFIRMED): the class kind and abstract aren't checked.
      • interface Main { static void main(String[] a) {} } becomes void main() {}, which doesn't compile.
      • Abstract classes, enums and records with components get an instance main the launcher can't instantiate.
    • Also (SUSPECTED, :114,129-132): the Spring Boot guard misses meta-annotations and launchers that call SpringApplication.run(App.class, args).
  • C7 UseVar* recipes

    Details
    • UseVarForTypeCast (CONFIRMED, :50-59,84-89): bypasses DeclarationCheck.isVarApplicable. Fields of anonymous classes get var, and so does String s[] = (String[]) o; neither compiles. Fold it into DeclarationCheck.
    • UseVarForObject (CONFIRMED, :59-73): Object o = "a"; o = 1; narrows the type and breaks the reassignment. Mirror the isReassigned guard from UseVarForGenericsConstructors:85.
    • SUSPECTED (DeclarationCheck.java:444-466):
      • Kotlin isn't excluded.
      • TYPE_USE annotations (for example JSpecify @Nullable) stay in front of var, which javac rejects.
      • A J.AnnotatedType loses its annotation.
    • Related: UseVarForGenericMethodInvocations 'mvn rewrite:run' crash #698.
  • C8 JavadocToMarkdownDocComment comments out code on the same line (Keep code out of converted Markdown doc comments and escaped tags visible #1345)

    Details
    • Where: M/lang/JavadocToMarkdownDocComment.java:98-102.
    • Problem (CONFIRMED): enum Color { /** Red */ RED, /** Green */ GREEN } becomes /// Red followed by RED, ... on the same line, so the code ends up inside the comment.
    • Also (SUSPECTED, :813-825): &lt;T&gt; becomes a raw <T>, which renders as an invisible HTML tag.
  • C9 DanglingDocCommentToBlockComment never converges on banner comments (Strip all leading asterisks in DanglingDocCommentToBlockComment (C9 of #1330) #1364)

    Details
    • Where: M/DanglingDocCommentToBlockComment.java:61-68, in UpgradeToJava25.
    • Problem (CONFIRMED): each pass strips one *, so /******** section ********/ keeps changing on every cycle.
    • Fix: strip the whole leading run of *, or skip comments whose text starts with **.
  • C10 Smaller lang items

    Details
    • UseTextBlocks (SUSPECTED, :86-89): only Kotlin is excluded. In Groovy the result is a GString that interpolates $.
    • MigrateClassNewInstanceToGetDeclaredConstructorNewInstance (SUSPECTED, :65-71): firstEnclosing(J.Try) also matches calls in finally or resources. Constructor exceptions are now wrapped in InvocationTargetException, which the description doesn't mention.
    • MigrateProcessWaitForDuration (SUSPECTED, :70-79): waitFor(Long.MAX_VALUE, DAYS) now throws ArithmeticException.
    • MigrateSecurityManagerMulticast (SUSPECTED, :52-57): the dropped second argument may have side effects. It could also be a declarative DeleteMethodArgument.
    • ExtractExplicitConstructorInvocationArguments (SUSPECTED, :122-131,207-216): a nested type gets an Outer.Inner import, and on a simple-name clash it binds to the wrong type. "Strictly behavior-preserving" over-claims.
    • LiteralOrVariable has no usages.

D. JDK API migrations (util, net, io, sql, logging, root)

  • D1 UseListOf / UseSetOf / UseMapOf: duplicates, nulls, foreign receivers

    Details
  • D2 Collections.* → List.of/Set.of/Map.of: undocumented semantic changes

    Details
    • MigrateCollectionsEmpty{List,Map,Set} (:53-60): sort, removeIf and clear were no-ops and now throw UOE; contains(null) now throws an NPE.

    • MigrateCollectionsSingleton{List,Map,Set}: a runtime-null element now throws an NPE.

    • MigrateCollectionsUnmodifiableSet (:56-71): any new X(Arrays.asList(..)) is accepted, so TreeSet/LinkedHashSet lose their order, duplicates throw, and {{ add(..) }} bodies are dropped. CONFIRMED.

    • MigrateCollectionsUnmodifiableList (:52-68):

      • unmodifiableList(asList(arr)) was a live view of arr and is now a copy.
      • There's no null check.
      • The result keeps the zero-argument List.of() method type.

      CONFIRMED.

    • Fix: bail on these shapes, and state the null/mutability differences in the descriptions.

    • Related: Migrate unmodifiableMap(..) to Map.of(..) #71 asks for unmodifiableMap(..) → Map.of(..); a new recipe would face the same null and duplicate-key hazards.

  • D3 Sequenced-collection recipes

    Details
    • ListFirstAndLast (CONFIRMED, :76-96): on an empty list, get(0) threw IndexOutOfBoundsException, but getFirst() throws NoSuchElementException. Document it.
    • StreamFindFirst (CONFIRMED, :57-58):
      • It NPEs on stream() with an implicit this.
      • isOfClassType(SequencedCollection) is an exact match, so List/Deque receivers never change. Use isAssignableTo.
    • Stale types: IteratorNext:60-64 and StreamFindFirst:61-65 clone the type of iterator()/stream(), so getFirst() returns Iterator<E>/Stream<E>.
    • YAML: the SequencedCollection renames (Y/java-version-21.yml:97-117) have no Java 21 gate and lack matchOverrides.
  • D4 ReplaceSystemOutWithIOPrint and MigrateStringReaderToReaderOf

    Details
  • D5 java.net recipes (Keep the unsigned TTL in setTTL and the byte type in getTTL migrations #1341)

    Details
  • D6 Logging, SQL and Base64

    Details
    • MigrateDriverManagerSetLogStream (CONFIRMED, :51-54): setLogStream(null), the usual way to turn logging off, becomes new PrintWriter(null), which is ambiguous and doesn't compile.
    • MigrateLoggerLogrbToUseResourceBundle (CONFIRMED, :51-60): ResourceBundle.getBundle(name) throws MissingResourceException, where the String overload logged without localization.
    • MigrateGetLoggingMXBeanToGetPlatformMXBean (CONFIRMED, :54-61): the ChangeType only runs in calling files, so standalone runs break call sites in other files. Move it to getRecipeList().
    • MigrateLoggerGlobalToGetGlobal (SUSPECTED, :43): the template has a stray ;, and the test relaxes methodInvocations. Replace it with ChangeStaticFieldToMethod.
    • UseJavaUtilBase64 (CONFIRMED JDK behaviour, :89-110): with the defaults, decodeBuffer → getDecoder() throws on line-wrapped input, and encodeBuffer output changes for inputs over 57 bytes. Map these to the MIME coder.
  • D7 Exception-type swaps narrow the catch and break throws

    Details
    • Where: M/ArrayStoreExceptionToTypeNotPresentException.java:52-58 and M/IllegalArgumentExceptionToAlreadyConnectedException.java:52-58.
    • Problem (CONFIRMED): the catch type is replaced rather than widened, so other exceptions of the old type in the try body are no longer caught. ChangeType also rewrites throw new X("m", e) in the catch body, and neither new exception has that constructor.
    • Fix: use a multi-catch, changing only the parameter's type tree, and skip when the new type is already caught.
  • D8 Root-package API recipes (Fix root-package API recipes reached by the Java 11 migration (D8 of #1330) #1366)

    Details
    • ReferenceCloneMethod:69 (CONFIRMED): passes the reference itself as the referent instead of ref.get(). For WeakReference<String> the output doesn't compile.

    • MXBeanRule:84-110 (CONFIRMED): applies to any *MBean class once one non-public interface exists in the file. public interface FooMBean becomes public public, and an abstract *MBean class loses abstract.

    • ReplaceComSunAWTUtilitiesMethods:121,127-128 (CONFIRMED):

      • An unchecked (J.FieldAccess) cast throws on a static-imported constant.
      • The super result is discarded.
      • The template has no closing ).

      The tests only use com.test.* patterns, never the shipped defaults.

    • ReplaceAWTGetPeerMethod:76-78,112-114 (CONFIRMED): resolves to the deprecated same-package ChangeMethodInvocationReturnType, whose visitor is a no-op, so the return type is never fixed.

    • JREThrowableFinalMethods:93-107 (SUSPECTED): also renames calls declared on java.lang.Throwable itself.

    • ChangeDefaultKeyStore (SUSPECTED, design): UsesJavaVersion(11,11) matches projects that have run on 11 for years, and hard-codes "jks".

    • AddMissingMethodImplementation: anonymous classes are not visited. See also AddMissingMethodImplementation generates wrong method for constructors #360.

  • D9 RemovedSecurityManagerMethods and RemovedSubjectMethods

    Details
    • M/RemovedSecurityManagerMethods.java:46-50 (CONFIRMED):

      • boolean b = sm.checkTopLevelWindow(w) becomes a void call.
      • AllPermission is far stronger than each original check.
      • There's no precondition and no test at all.
    • RemovedSubjectMethods (Y/java-version-21.yml:119-135, CONFIRMED no-op):

      • The zero-argument patterns never match getSubject(AccessControlContext) or doAs(Subject, ..).
      • Renaming alone wouldn't compile anyway (callAs takes a Callable).
      • The display name has a stray backtick.

      Delete it, or write a Java recipe.

  • D10 SimplifyMicrometerMeterTags produces broken code

    Details
    • Where: M/metrics/SimplifyMicrometerMeterTags.java:45-66. It is unwired and has no precondition.
    • Problem (CONFIRMED):
      • .tags("k", "v") becomes .tag("k"[0], "k"[1]).
      • With four or more tags, only the first pair is kept.
      • tags(new String[0]) is renamed and no longer compiles.
    • Fix: fix it and move it to rewrite-micrometer, or delete it.

E. Guava

  • E1 NoGuavaJava11: ImmutableSet/ImmutableMap → Set.of/Map.of change runtime behaviour

    Details
    • Where: M/guava/AbstractNoGuavaImmutableOf.java:93-130 and Y/no-guava.yml:104-109.
    • Problem (CONFIRMED, JDK spec):
      • Set.of(a, b) throws IAE when a.equals(b); Guava deduplicates.
      • The iteration order of Set.of/Map.of/copyOf is unspecified and randomized per JVM, while Guava keeps insertion order. JSON key order, toString and test assertions change.
      • contains(null)/get(null) throw an NPE on all JDK immutables, including List.of.
    • Fix: make the Set and Map conversions opt-in, or limit them to distinct compile-time constants, and document the null-query difference.
  • E2 AbstractNoGuavaImmutableOf crashes and wrong targets

    Details
    • Varargs (CONFIRMED, :202-225): getParameterTypes().get(index) throws IndexOutOfBoundsException when the call is a vararg argument, for example String.format("%s %s", "a", ImmutableList.of("b")) or SLF4J log.info(..).
    • Lambdas (CONFIRMED, :193-201): a return inside a lambda is checked against the enclosing method's return type.
    • Select branch (CONFIRMED, :208-209): ImmutableSet.of(..).asList() becomes Set.of(..).asList().
    • SUSPECTED:
      • The rewritten call keeps the ImmutableList.of method type.
      • ImmutableList.of(arr) gives a one-element list, while List.of(arr) spreads the array.
      • convertReturnType=true retypes variables that still use Guava-only API.
    • Metadata: getShortType (:71-73) renders descriptions as "Replaces .common.collect.ImmutableList.of(..)" (visible in recipes.csv:147-152).
  • E3 NoGuava emits Java 9–16 APIs with no version gate

    Details
    • Problem (CONFIRMED): run on a Java 8 project, these produce code that doesn't compile:
      • NoGuavaIterablesAnyFilter:69: Stream.toList() (16).
      • NoGuavaOptionalAsSet:52: Optional.stream() (9).
      • Predicates.not → Predicate.not (Y/no-guava.yml:205-207, 11).
      • InlineGuavaMethods: repeat (Y/inline-guava-33-methods.yml:228, 11) and optional.stream() (:89-107).
      • Picnic InputStreamRulesRecipes (Y/no-guava.yml:86): transferTo, readAllBytes and others.
    • Optional on Java 8 (CONFIRMED, Y/no-guava.yml:184-186): or(Optional) is renamed to orElse(Optional), because PreferJavaUtilOptionalOrSupplier only runs on 9+. Narrow the pattern to or(java.lang.Object).
    • Fix: move these into NoGuavaJava11/NoGuavaJava21, or add UsesJavaVersion preconditions.
  • E4 PreferCharCompare targets java.lang.Char (Rewrite Chars.compare to Character.compare instead of nonexistent Char.compare #1333)

    Details
    • Where: Y/no-guava.yml:306-318.
    • Problem (CONFIRMED): java.lang.Char doesn't exist, so Chars.compare(a, b) becomes Char.compare(a, b) when run standalone. Inside NoGuava, InlineGuavaMethods (Y/inline-guava-33-methods.yml:136) rewrites the call first, which hides the bug.
    • Fix: use java.lang.Character, or delete it together with the other duplicates of InlineGuavaMethods (PreferInteger/Long/ShortCompare, PreferMath*Exact). Add a test.
  • E5 Function/Supplier/Optional/Predicate type swaps leave Guava consumers broken

    Details
    • Unconditional swaps (CONFIRMED, Y/no-guava.yml:145-157,160-192,211-223): the ChangeTypes apply unconditionally.

      • Consumers break: Lists.transform(l, F), Maps.transformValues, Futures.transform and Suppliers.memoize now get java.util.function types.
      • Producers break: Iterables.tryFind and Enums.getIfPresent still return a Guava Optional.

      Reuse the "file uses such an API" exclusion from NoGuavaPredicate.

    • Predicates (CONFIRMED):

      • NoGuavaPredicatesAndOr:95-101, NoGuavaPredicatesEqualTo:70-78 and Predicates.not produce a java.util.function.Predicate regardless of NoGuavaPredicate's exclusion.
      • NoGuavaPredicatesAndOrTest:53-59 and NoGuavaFunctionsComposeTest:61-79 expect output that doesn't compile.
      • NoGuavaPredicatesEqualTo:70-71 builds its type witness from toString(), giving Predicate.<int>isEqual(5).
    • NoGuavaPredicate:66,74-79: the precondition only matches a Predicate in the last parameter, and it checks per file.

    • Related: PreferJavaUtilFunction should not convert when used specifically as Guava type #436, and NoGuava Recipe – Summary of the analysis on the Sirius-Desktop codebase #928, whose Sirius-Desktop analysis collects many of these shapes (plus some from E2 and E6).

  • E6 Smaller Guava bugs

    Details
    • NoGuavaPrimitiveAsList:59-64 (CONFIRMED): Ints.asList(intArray) becomes List<int[]>, and Longs.asList(1, 2) becomes List<Integer>.
    • NoGuavaCreateTempDir:65-77 (CONFIRMED): calls inside lambdas, or inside the catch/finally of a try that catches IOException, are converted and leave an unhandled IOException.
    • NoGuavaSetsFilter:59 (CONFIRMED): toCollection(TreeSet::new) drops the comparator. Also see @ExpectedToFail at T/guava/NoGuavaSetsFilterTest.java:106.
    • NoMapsAndSetsWithExpectedSize:64-91 (CONFIRMED): gated on Java 19, but it emits new HashMap<>(n) (capacity) instead of HashMap.newHashMap(n).
    • NoGuavaRefaster:76-79 (CONFIRMED): static-imports String.valueOf, which is shadowed inside enums.
    • PreferJavaUtilOptional (CONFIRMED): get() now throws NoSuchElementException instead of IllegalStateException, and static Optional.toJavaUtil(x) isn't handled.
    • NoGuavaDirectExecutor:50-56 (SUSPECTED): no guard for .execute(r) or var usage.
    • PreferJavaStringJoinVisitor:64-69 (SUSPECTED): the template lacks its closing ). Joiner NPEs on null elements, while String.join prints "null".
    • Lazy views become eager copies (Collections2.transform, Iterables.filter, Sets.filter). This is undocumented.
    • Springfox bump (Y/no-guava.yml:33-37, SUSPECTED): check whether 2.10.x removed @EnableSwagger2.
    • PreferJavaUtilObjectsEquals (CONFIRMED by probe, Y/no-guava.yml:227-240): in a class with its own equals(Object), the static import of Objects.equals is shadowed, so equals(a, b) doesn't compile. See PreferJavaUtilObjectsEquals does not check for existing equals method #434.

F. Lombok

  • F1 UseLombokSetter deletes setters that do more than assign the parameter (Keep setters that do more than assign their parameter in UseLombokSetter #1332)

    Details
    • Where: M/lombok/LombokUtils.java:122-178.
    • Problem (CONFIRMED): isSetter checks the name, owner and parameter type, but never the right-hand side. this.name = name.trim(), this.items = new ArrayList<>(items) and this.x = 5 are all replaced by a plain @Setter, so the logic is lost silently.
    • Fix: require the assigned value to be a J.Identifier referring to the method's own parameter.
  • F2 boolean isFoo fields: wrong setter name

    Details
  • F3 AdoptLombok{Getter,Setter}MethodNames rename methods that aren't accessors

    Details
    • Where: M/lombok/LombokUtils.java:93-108,184-212.
    • Problem (CONFIRMED): any identifier or field access is accepted, including static constants, other objects' fields, enum constants and outer-class fields. So String version() { return VERSION; } becomes getVERSION(), and ownerName() { return owner.name; } becomes getName().
    • Fix: reuse the owner, non-static and this.-only checks from isGetter/isSetter.
  • F4 Constructor recipes drop annotations

    Details
    • UseRequiredArgsConstructor / UseAllArgsConstructor (CONFIRMED, M/lombok/LombokUtils.java:282-335): parameter annotations and throws are ignored, so S(@Qualifier("primary") Foo foo) becomes @RequiredArgsConstructor and the qualifier is lost.
    • UseNoArgsConstructor (CONFIRMED, :47-70): drops constructor annotations (@Inject, @JsonCreator, @Deprecated), comments in the body (A() { // for JPA }), and throws.
    • SUSPECTED:
      • Varargs parameters (:313) produce an A(String[]) constructor.
      • Non-Lombok @NonNull annotations (:235-258) change Lombok's set of required fields, so callers break.
  • F5 FieldAnnotator keeps an existing @Getter/@Setter and its access level

    Details
    • Where: M/lombok/FieldAnnotator.java:49-71.
    • Problem (CONFIRMED): with @Getter(AccessLevel.PROTECTED) int foo; public int getFoo(), the public getter is deleted and the getter becomes protected. In a multi-variable declaration, the first accessor's access level wins for every variable.
    • Fix: bail in both cases.
  • F6 LombokBestPractices breaks modules without Lombok, and Java 8 projects

    Details
    • No dependency check (CONFIRMED, Y/lombok.yml:17-48): there's no ModuleHasDependency org.projectlombok:lombok precondition and nothing adds the dependency, so non-Lombok modules get Lombok annotations that don't compile. The standalone UseLombok* and Use*ArgsConstructor recipes behave the same.
    • Java 8 (CONFIRMED, Y/lombok.yml:27, M/lombok/LombokValToFinalVar.java:53-60): UpdateLombokToJava11 runs val → final var with no Java 10 gate.
    • Fix: add the dependency precondition and a HasJavaVersion gate. See also A1 and B7.
  • F7 UseLombokLogAnnotations: nested classes and name clashes

    Details
    • Nested classes (CONFIRMED, M/lombok/log/LogVisitor.java:48-64): converting a logger in a nested class also annotates every enclosing class. If one of them is an interface, it doesn't compile.
    • Name clash (CONFIRMED, :97-99): the field is renamed to log without a clash check, so LOGGER.info(log) with a log parameter breaks.
    • SUSPECTED:
      • lombok.config keys are never read: lombok.log.fieldName, lombok.accessors.*, lombok.getter.noIsPrefix.
      • The plain JavaIsoVisitors also hit Groovy and Kotlin.
      • synchronized getters, throws clauses and Javadoc on removed methods are dropped.
  • F8 LombokValueToRecord

    Details
    • extends (CONFIRMED, :110-117): @Value class A extends B becomes record A(..) extends B.
    • Arrays (CONFIRMED, :190-202,315-332): array fields lose Lombok's deep equals/hashCode/toString.
    • toString format: without useExactToString, A(a=1) becomes A[a=1].
    • Stale types (:229-236,334-340): call sites renamed to x() keep the method type, and the record's class type is replaced by an empty ShallowClass.
    • Description: should warn that getter call sites and toString change.

G. Jakarta EE / Java EE / JPA / JSpecify

  • G1 HttpSessionInvalidate replaces session.invalidate() with request.logout() (Keep HttpSession.invalidate() when adding HttpServletRequest.logout() #1334)

    Details
    • Where: M/javax/HttpSessionInvalidate.java:51-75, reached by every JavaxMigrationToJakarta run via javaee8 → javaee7 → javaee6 (Y/java-ee-6.yml:28).
    • Problem (CONFIRMED): the description says "Add … logout", but the code replaces invalidate(). logout() clears authentication but leaves the session alive, so logout and reset handlers silently stop invalidating sessions. That is a security regression. logout() also throws the checked ServletException. T/javaee/HttpSessionInvalidateTest.java:39-55 encodes the replacement.
    • Also:
      • NPE when there's no enclosing method declaration, or its type is missing (:53-54,83).
      • The precondition or(UsesMethod, UsesType(HttpServletRequest)) lets every servlet file through.
    • Fix: keep invalidate() and add logout() before it only when ServletException is handled, or make the recipe search-only.
  • G2 OpenJPA → EclipseLink recipes annotate the wrong elements and crash

    Details
    • AddTransientAnnotationToCollections:57-75 (CONFIRMED): parameters and locals get @Transient, which has @Target({METHOD, FIELD}). It NPEs on untyped lambda parameters (:59) and unresolved annotations (:64).
    • AddTransientAnnotationToEntity:85-107 (CONFIRMED): no enclosing-class check, so fields, parameters and locals of entity type in services and DTOs get @Transient. It also has the same NPE.
    • AddTableGenerator:71-81 (CONFIRMED): on getter-annotated entities, @GeneratedValue(strategy = IDENTITY) becomes TABLE without a @TableGenerator.
    • AddTransientAnnotationToPrivateAccessor:84-90 (CONFIRMED): NPEs on a private no-arg constructor. classVars (:54-66) is clobbered by nested classes.
    • RemoveTemporalAnnotation:96 (CONFIRMED): NPEs on lambda parameters.
    • AddDefaultConstructorToEntityClass:47-58 (CONFIRMED): adds public X() {} to entities that have no constructor, and acts on outer classes of nested entities.
    • RemoveEmbeddableId:113-116 (SUSPECTED): unchecked (JavaType.Class) cast.
  • G3 RemoveBeanIsNullable cleans up the whole file

    Details
    • Where: M/jakarta/RemoveBeanIsNullable.java:47-51.
    • Problem (CONFIRMED): RemoveUnusedLocalVariables and SimplifyConstantIfBranchExecution run on the whole compilation unit, which deletes unrelated code. Also:
      • foo(bean.isNullable()) becomes foo( false).
      • A select with side effects is dropped.
  • G4 UpdateGetRealPath misses nested calls (Rewrite nested and unqualified getRealPath(String) calls for Servlet 6 #1339)

    Details
    • Where: M/jakarta/UpdateGetRealPath.java:41-51.
    • Problem (CONFIRMED): there's no super call, so Paths.get(req.getRealPath("/")) is never migrated and fails on Servlet 6.
    • Also (SUSPECTED): with no select, as in a HttpServletRequestWrapper subclass, null is passed to #{any()}.
  • G5 BeanDiscovery, JpaCacheProperties and AddStaticVariableOnProducerSessionBean

    Details
    • BeanDiscovery:67-72 (CONFIRMED, test encodes it): an explicit bean-discovery-mode="none" or "annotated" is overwritten with "all". Keep an explicit mode.
    • JpaCacheProperties:93-102 (CONFIRMED): version is read from <persistence-unit>, so it is always null. JPA 1.0 files get a <shared-cache-mode> their schema forbids, and the EclipseLink branch (:157-179) is dead. SUSPECTED: every unit gets NONE, including Hibernate apps, which silently disables @Cacheable.
    • AddStaticVariableOnProducerSessionBean:81-101 (CONFIRMED dead): it only matches jakarta types, but runs in javaee7 on javax code. If it did fire, a static @Produces @PersistenceContext field would break.
  • G6 WsWsocServerContainerDeprecation never renames inside JakartaEE10

    Details
    • Where: Y/jakarta-ee-10.yml:201-214, run after JavaxMigrationToJakarta (:27 vs :34).
    • Problem (CONFIRMED): the method pattern names the javax.servlet parameters, which have already been renamed. The ChangeType still applies, giving ServerContainer.doUpgrade(..), which doesn't exist. The standalone test passes.
    • Fix: use doUpgrade(..). Even then, upgradeHttpToWebSocket is an instance method, so the change needs a TODO comment or a Java recipe.
  • G7 Faces 3 FindAndReplace turns XML into plain text, so the Faces 4 updates are skipped

    Details
    • Where: Y/jakarta-faces-3.yml:126-129 (faces-config), :154-157 (taglib) and :182-189 (web-fragment), versus Y/jakarta-faces-4.yml:141,161,181.
    • Problem (CONFIRMED by tracing): any file containing javax. becomes a PlainText, and the later ChangeTagAttribute version and schemaLocation updates skip it, so these files stay at 3.0/5.0 under JakartaEE10/11.
    • Fix: web.xml was already moved to ChangeTagValue; do the same for these three.
    • Probe: JakartaEE10 on a 2.3 faces-config with <el-resolver>javax.el.BeanELResolver</el-resolver>.
  • G8 MigrateFastjsonForJakarta10: swapped enums and a non-recursive package change (Map fastjson features to the right fastjson2 enum and migrate @JSONField imports #1342)

    Details
    • Where: Y/jakarta-ee-10.yml:866-874.
    • Problem (CONFIRMED):
      • SerializerFeature maps to JSONReader.Feature and parser.Feature to JSONWriter.Feature, the wrong way round.
      • ChangePackage com.alibaba.fastjson isn't recursive, so annotation.JSONField keeps the old package after the dependency swap.
    • Also: it has no tests. See also A6.
  • G9 CDI and JSF recipes

    Details
    • AddScopeToInjectedClass / AnnotateTypesVisitor (CONFIRMED, unwired):
      • It adds @Dependent to classes that already have a scope (a CDI DefinitionException), and to interfaces and abstract classes.
      • It throws ClassCastException on @Inject T t.
    • UpdateManagedBeanToNamed:56-79 (SUSPECTED): an unscoped @ManagedBean is request-scoped, but an unscoped @Named is @Dependent. eager = true is dropped. Add @RequestScoped. See also FacesManagedBeansRemoved for Faces 4.0 recipe updates #1023.
    • JacksonProviderOverrides:45-46: targets _configForWriting(ObjectMapper, Annotation[]), which no jackson-jaxrs-base since 2.3 has. The test stub models a shape that doesn't exist. Delete it, or test against the real jar.
    • AddApiDependencyForImports:89-92 (SUSPECTED): JSR-305 javax.annotation.Nullable imports add jakarta.annotation-api.
  • G10 JSpecify migrations invert array nullness and keep invalid attributes

    Details
    • MoveAnnotationToArrayType:62,95 (CONFIRMED from the parser): public @Nullable String[] f() is a J.AnnotatedType and is skipped. The later ChangeType then flips "nullable array" into "nullable elements".
    • Y/jspecify.yml:114-115 (CONFIRMED via javap): the comment claims Jakarta's @Nullable targets TYPE_USE, but it has no @Target, so Jakarta arrays get the same inversion.
    • Attributes (CONFIRMED, Y/jspecify.yml:78-93,146-153,236-243): ChangeType keeps them, and none compile on JSpecify: JetBrains @NotNull("msg"), Micronaut @Nullable(inherited = ..), javax @Nonnull(when = ..). when = MAYBE really means nullable.
    • @ParametersAreNonnullByDefault → @NullMarked (:90-93, SUSPECTED): @NullMarked also covers returns and fields.
    • See also B8.

H. Search recipes & data tables

  • H1 AboutJavaVersion reports the target as the source version (Fix AboutJavaVersion source version and per-run dedup (H1 of #1330) #1365)

    Details
    • Where: M/search/AboutJavaVersion.java:75.
    • Problem (CONFIRMED): the source-compatibility column uses getMajorReleaseVersion(); it should use getMajorVersion(). The test uses equal source and target, so it can't catch this.
    • Also: the transient Set seenSourceSets field (:39) is shared across runs.
  • H2 DTO and javax search recipes

    Details
    • DontOverfetchDto (CONFIRMED, M/DontOverfetchDto.java:60-145):

      • Signatures change without updating callers.
      • Uses like feed(dog)/return dog are left dangling.
      • dog.getName() can become an unbound name.
      • The test's TypeValidation.none() hides an inlined java.lang.String FQN.

      Restrict it to private methods, make it search-only, or delete it.

    • FindDataUsedOnDto:51-57 (CONFIRMED): no super call, so dto.getAddress().getCity() records only the outer call.

    • FindDtoOverfetching:53-83 (SUSPECTED): uses of all DTO parameters are merged, and DTOs passed on whole are still flagged.

    • FindInternalJavaxApis:58-80 (SUSPECTED): reports JDK-only javax APIs (Cipher, javax.sql, javax.swing).

    • FindJavaVersion:57 / PlanJavaMigration:51: preVisit without stopAfterPreVisit() walks every node.

I. Type attribution & TypeValidation.none()

J. Preconditions & declarative

  • J1 Add preconditions (Add missing preconditions and Java version gates to recipes (J1) #1349)

    Details
    • Root package: RemovedSecurityManagerMethods:37, ReplaceComSunAWTUtilitiesMethods:110, ReplaceLocalizedStreamMethods:62.
    • jakarta: ApplicationPathWildcardNoLongerAccepted:38, InvocationContextGetConstructor:39, JacksonProviderOverrides:43, JerseyLoggingFilterToFeature:36, JettyConnectorGetPort:38, RemoveBeanIsNullable:43, UpdateAddAnnotatedTypes:35, UpdateBeanManagerMethods:41, UpdateGetRealPath:36, UpdateAnnotationAttributeJavaxToJakarta:46.
    • lang: MigrateClassLoaderDefineClass:44, ThreadStopUnsupported:47.
    • metrics: SimplifyMicrometerMeterTags:44.
    • DTO recipes: DontOverfetchDto:59, FindDataUsedOnDto:45, FindDtoOverfetching:47 (UsesType from the option).
    • Edit visitor only: AddTransientAnnotationToEntity:85.
    • Lombok: the five Use*Log recipes (UsesMethod(factoryMethodPattern)).
    • Java version: MigrateProcessWaitForDuration:44 (add UsesMethod), ReplaceSystemOutWithIOPrint, OptionalStreamRecipe, JavaConcurrentAPIs (Java 9).
  • J2 UsesType → UsesMethod(MATCHER) reusing the visitor's own matcher (Use UsesMethod preconditions with the visitors' own matchers (J2) #1346)

    Details
    • net/URLConstructorsToNewURI:46
    • net/URLConstructorToURICreate:55
    • nio/file/RedundantUtf8Charset:60
    • util/RemoveFinalizerFromZip:55-57
    • JREThrowableFinalMethods:63: UsesType<>("java.lang.Throwable", true) matches nearly every file.
    • guava/AbstractNoGuavaImmutableOf:91
    • HttpSessionInvalidate:44-47: already UsesMethod since Keep HttpSession.invalidate() when adding HttpServletRequest.logout() #1334.

    Fixed in Use UsesMethod preconditions with the visitors' own matchers (J2) #1346.

  • J3 Replace isAcceptable with preconditions (Use JavaFileChecker precondition in ExtractExplicitConstructorInvocationArguments #1337)

    Details
    • M/lang/ExtractExplicitConstructorInvocationArguments.java:67: use not(KotlinFileChecker) and not(GroovyFileChecker), as SwitchCaseReturnsToSwitchExpression does.
  • J4 Make it declarative (Make java.net/logging/lang and Guava factory recipes declarative (J4) #1350)

    Details
    • M/net/MigrateHttpURLConnectionHttpServerErrorToHttpInternalError.java → ReplaceConstantWithAnotherConstant. This also removes an unconditional doAfterVisit.
    • M/logging/MigrateLoggerGlobalToGetGlobal.java → ChangeStaticFieldToMethod.
    • M/lang/MigrateSecurityManagerMulticast.java → DeleteMethodArgument, if side effects are acceptable (C10).
    • Guava Refaster candidates: the Lists/Maps/Sets factory recipes, NoGuavaIterablesAll, NoGuavaCollections2Transform.
    • Compatibility: keep the recipe names as YAML recipes so existing references don't break.

K. Mechanical polish (low risk, good first issues)

Items K1–K12 are in the first comment below, to keep this issue under GitHub's size limit.

L. Open questions & coverage

  • L1 Decide where Singleton goes before polishing

    Details
    • Status: 257 Singleton preconditions; 212 are on non-top recipes.
    • Why it matters: the non-top ones currently dedupe the diamond includes (Faces 2→3 reached four times; see A5). Remove the diamonds before stripping them.
    • Also: the DataNucleus files (Y/datanucleus-*.yml:19) have none, not even on their top recipes.
  • L2 Test what ships

    Details
    • Status: 120 of 292 YAML recipes are loaded by name in a test. All but 2 of the rest sit under a tested composite, but those composite tests rarely exercise the sub-recipes.
    • No test at all:
      • RemovedSecurityManagerMethods
      • AddJDeprScanPlugin
      • AddSuppressionForIllegalReflectionWarningsPlugin
      • PlanJavaMigration
      • InlineGuavaMethods (79 entries)
      • PreferChar/Integer/Long/ShortCompare
      • PreferMath*Exact
      • PreferJavaUtilFunction/Supplier
      • PreferJavaNioCharsetStandardCharsets
      • openJPAToEclipseLink
    • Highest-value gaps:
      • the confirmed bugs in E4, B8, G6, G7, G8 and D9
      • Lombok on Gradle (B7)
      • UpgradeToJava25 on a Kotlin 1.x module (B2, D4)
      • the Java 25 AccessController/RemoveSecurityPolicy/RemoveSecurityManager recipes, which also run UnnecessaryCatch/UnnecessaryThrows file-wide
      • UpdateApacheCommonsEmailDependencies
      • NoGuava with javaVersion(8) (E3)
    • Not testing the shipped defaults: ReplaceComSunAWTUtilitiesMethodsTest:30 and ReplaceAWTGetPeerMethodTest:30 use com.test.* patterns.
    • Tests that assert wrong output: TTL (D5), Micrometer (D10), HttpSessionInvalidate (G1), BeanDiscovery (G5), AddLombokMapstructBinding (B1), and the expected outputs listed in E5 that don't compile.
  • L3 Verify upstream before fixing

    Details
    • GraalVM (B10): which resource-config.json format accepts globs?
    • Kotlin (B2): confirm the maximum jvmTarget per Kotlin version.
    • Docker (B3): which eclipse-temurin variants exist for 25?
    • Springfox (E6): does 2.10.x drop @EnableSwagger2?
    • RemovedSecurityManagerMethods: were these removed in JDK 10 or deprecated in 11? The description says 11.

Activity

  1. timtebeek commented on Oct 24, 2026

    @timtebeek
    MemberAuthor

    Continued from the issue body.

    K. Mechanical polish (low risk, good first issues)

    • K1 Typed #{any(..)} placeholders instead of #{any()}

      Details
      • Scope: about 40 sites. Do the receiver positions first.
      • Receiver positions: ReplaceComSunAWTUtilitiesMethods:138-163, jakarta/UpdateGetRealPath:43, lang/MigrateClassNewInstanceToGetDeclaredConstructorNewInstance:70.
      • Arguments:
        • guava/NoMapsAndSetsWithExpectedSize:64,73,82,91
        • util/UsePredicateNot:68
        • util/MigrateCollectionsSingletonSet:57
        • io/ReplaceSystemOutWithIOPrint:60
        • logging/MigrateLoggerLogrbToUseResourceBundle:52
        • jakarta/JettyConnectorGetPort:41
      • Element lists:
        • util/UseSetOf:78,109
        • UseListOf:93,125
        • UseMapOf:83-86,132-135
        • MigrateCollectionsUnmodifiableSet:66
        • UseEnumSetOf:102
        • MigrateCollectionsSingletonMap:62
        • guava/NoGuavaSetsNewHashSet:79
        • NoGuavaPrimitiveAsList:59
        • AbstractNoGuavaImmutableOf:113,116
      • Switch templates in lang are legitimately untyped; leave them.
    • K2 Drop forced imports, and contextSensitive() where it isn't needed (Drop forced imports and unneeded contextSensitive() from JavaTemplate recipes #1347)

      Details
      • Forced imports:
        • ReplaceComSunAWTUtilitiesMethods:125,126
        • net/MigrateURLDecoderDecode:60
        • net/MigrateURLEncoderEncode:61-63 (AddImport(.., false), with a "not sure why" comment)
      • Unneeded contextSensitive():
        • UseJavaUtilBase64:129
        • ReferenceCloneMethod:73
        • the two URL recipes
        • MigrateClassLoaderDefineClass:46, which also holds its JavaTemplate in a field
    • K3 Remove //noinspection lines

      Details
      • Sites by file:
        • JpaCacheProperties:131
        • AddMockitoJavaAgentToMavenSurefirePlugin:157,166
        • UseJavaUtilBase64:118
        • net/URLConstructorToURICreate:159
        • search/FindInternalJavaxApis:64
        • javax/AnnotateTypesVisitor:46
        • jspecify/MoveAnnotationToArrayType:80,113
    • K4 private static final UPPER_SNAKE matchers and FQNs; no per-visit allocation (Hoist visitor matchers and regexes into private static final constants (K4 of #1330) #1357)

      Details
      • Instance fields in anonymous visitors:
        • RemovedSecurityManagerMethods:39-42
        • jakarta/JerseyLoggingFilterToFeature:38-41
        • UpdateAddAnnotatedTypes:37
        • UpdateBeanManagerMethods:43-44
        • UpdateGetRealPath:38
        • JettyConnectorGetPort:40
        • JacksonProviderOverrides:45
        • javax/AddTableGenerator:48-49
        • AddScopeToInjectedClass:62
        • JREThrowableFinalMethods:68
        • AddMockitoJavaAgentToMavenSurefirePlugin:73
      • Locals in getVisitor():
        • util/OptionalNotEmptyToIsPresent:46 (also misnamed)
        • OptionalNotPresentToIsEmpty:46
        • javax/HttpSessionInvalidate:42-43
        • search/FindInternalJavaxApis:58
        • javax/RemoveTemporalAnnotation:65-67
        • ArrayStoreExceptionToTypeNotPresentException:44
        • IllegalArgumentExceptionToAlreadyConnectedException:44
      • Created per call:
        • lombok/LombokUtils:247
        • Pattern in UpgradeDockerImageVersion:197 and UpdateSdkMan:92
      • Non-static: javax/UseJoinColumnForMapping:33-34.
      • camelCase constants:
        • net/URLConstructorsToNewURI:34-36
        • URLConstructorToURICreate:45
        • util/OptionalStreamRecipe:39-42
        • guava/NoGuavaCreateTempDir:49
        • ReplaceLocalizedStreamMethods:64
    • K5 Use the Comments trait instead of hand-built comments (Use the Comments trait instead of hand-built comments (K5 of #1330) #1355)

      Details
      • Trait: org.openrewrite.trait.Comments, incubating since 8.86. It is idempotent and supports Placement.FIRST_CHILD.
      • Sites:
        • lang/ThreadStopUnsupported:68-76
        • CommentJava24KotlinCap:122-150 (Xml.Comment plus hand-rolled hasCapComment/removeCapComment)
        • io/AddInputStreamBulkReadMethod:102: emits a transient SearchResult. A TODO comment would persist.
    • K6 ListUtils, JavaIsoVisitor, assert and restating comments (Use ListUtils, JavaIsoVisitor and null checks, drop restating comments (K6 of #1330) #1356)

      Details
      • ListUtils.filter:
        • lombok/LombokValueToRecord:354-359
        • MXBeanRule:104-108
      • JavaIsoVisitor:
        • AddStaticVariableOnProducerSessionBean:82
        • MXBeanRule:56,67
        • jakarta/UpdateGetRealPath:37
      • assert in recipe code:
        • ReplaceAWTGetPeerMethod:79,115
        • lombok/AdoptLombok{Getter,Setter}MethodNames:104
        • LombokValueToRecord:97,335
        • guava/PreferJavaStringJoinVisitor:59
        • search/ModuleHasKotlinSource:76
      • Class javadoc that repeats the description:
        • lang/var/UseVarForConstructors:31-39
        • search/FindLocaleDateTimeFormats:33-45
        • MigrateGraalVMResourceConfig:36-60
        • jakarta/UpgradeMavenEjbPluginConfiguration:27-35
        • AddJDeprScanPlugin:28-31
        • AddSuppressionForIllegalReflectionWarningsPlugin:29-32
        • UpgradeJavaVersion:65-69
      • Restate-the-code comments (sample):
        • LombokUtils (about 17)
        • MigrateGraalVMResourceConfig
        • HttpSessionInvalidate
        • RemoveTemporalAnnotation
        • AddColumnAnnotation
        • UseVarForGenericMethodInvocations:137,141
        • MigrateMainMethodToInstanceMain:95,106,120
      • Missing @Option: JREThrowableFinalMethods:41-48, UseJavaUtilBase64:36 (sunPackage).
    • K7 Java display names and descriptions (Polish Java recipe display names and descriptions (K7 of #1330) #1359)

      Details
      • Typos:
        • @Slf4 instead of @Slf4j: lombok/log/UseSlf4j:31,36.
        • "lombok": UseLogRecipeTemplate:31.
        • "inSecurityManager": RemovedSecurityManagerMethods:29.
        • Garbled text: util/RemoveFinalizerFromZip:46.
        • @Inject': AddScopeToInjectedClass:39.
        • "It is Replaced": UpdateAddAnnotatedTypes:32.
        • Embedded \n: AddColumnAnnotation:40-43.
        • EnumSet of(..): UseEnumSetOf:51.
        • java.lang.Char: E4.
        • Not a real signature: PreferJavaUtilOptionalOrSupplier:41.
      • Not sentence case:
        • AddMockitoJavaAgentToMavenSurefirePlugin:61
        • AddJDeprScanPlugin:41
        • AddSuppressionForIllegalReflectionWarningsPlugin:48
        • AddSurefireFailsafeArgLine:44
        • LombokValueToRecord:55
        • SwitchCaseAssignmentsToSwitchExpression:43
        • UseVarForGenericsConstructors:37
        • UpdateSdkMan:66 ("SDKMAN!")
      • Not verb-first:
        • MXBeanRule:48
        • the javax/Add*/Remove* JPA recipes
        • IfElseIfConstructToSwitch:50
        • OptionalStreamRecipe:34
        • UpdateGetRealPath:30
        • AddMissingMethodImplementation:53
        • AddStaticVariableOnProducerSessionBean:41
        • BeanDiscovery:38
      • Missing backticks: about 15 sites, e.g.:
        • InvocationContextGetConstructor:33,36
        • HttpSessionInvalidate:36
        • MigrateGraalVMResourceConfig:63
        • AddSurefireFailsafeArgLine:46
        • ChangeDefaultKeyStore:41
        • IllegalArgumentExceptionToAlreadyConnectedException:40
        • AddJakartaXmlBindRuntime:45
      • Inaccurate:
        • HttpSessionInvalidate:38 says "Add".
        • AddJaxbRuntime:66-70 names the wrong scopes.
        • ApplicationPathWildcardNoLongerAccepted:32 says "trailing slash".
        • RemoveBeanIsNullable:38 contradicts itself.
        • NoMapsAndSetsWithExpectedSize says "Java 19+ methods".
        • StringFormatted:44-47 option text.
        • UseVarForGenericMethodInvocations:40-41 says "open rewrite cannot handle them correctly ATM".
        • ThreadStopUnsupported:42-44 has a dangling " .".
      • Options:
        • The seven options in ReplaceComSunAWTUtilitiesMethods:35-76 share one display name, as do the two in ReplaceLocalizedStreamMethods:34-44.
        • AboutJavaVersion:41 has no display name.
        • UpdateAnnotationAttributeJavaxToJakarta:37-43: signature is required = false but NPEs when null.
    • K8 YAML display names and descriptions (Polish YAML recipe display names and descriptions (K8 of #1330) #1360)

      Details
      • Not sentence case (about 30):
        • "Lombok Best Practices" (Y/lombok.yml:19)
        • "Remove Security AccessController"/"Policy"/"SecurityManager" (Y/java-version-25.yml:73,94,115)
        • "Find Virtual Thread opportunities"
        • "OmniFaces Namespace Migration"
        • "Update Plugins for …"
        • "Migrate JAXB-WS Plugin"
        • "Managed Beans"
        • every "… Dependencies to x" title in jakarta-ee-10/11, jakarta-faces-4/41 and java-ee-8
        • "Migrate to JavaEE6/7/8"
        • "Update Jetty9 to Jetty12"
      • Typos:
        • "Jakata" (JavaxXmlToJakartaXmlXJCBinding)
        • "API's" (DeprecatedCDIAPIsRemoved40)
        • "search fro" (Y/java-virtual-threads.yml:42)
        • double spaces (Y/jakarta-ee-10.yml:333, Y/java-version-17.yml:292)
        • a stray backtick (Y/java-version-21.yml:121)
        • an unclosed backtick (Y/java-lang-var.yml:22-23)
      • Inaccurate:
        • ApacheDefaultProvider says "Flags", but it rewrites.
        • JakartaEE11 says "flagging".
        • MigrateZipErrorToZipException says "Java 9 or higher", but it is gated on 25.
        • AddJaxwsDependencies (Y/java-version-11.yml:140) mentions javax.xml.bind.
        • NoGuavaJava11/NoGuavaJava21 have copy-pasted descriptions.
      • Descriptions that point at other recipes:
        • UseVar
        • AddLombokMapstructBindingMavenDependencyOnly (Y/java-version-17.yml:388)
        • UpgradeBuildToJava24ForKotlin1x
        • AddAspectJToolsToAspectJMavenPlugin
      • Description identical to the display name:
        • the Update*Dependencies recipes in the Jakarta and Faces files
        • all MigrateFrom* in Y/jspecify.yml
        • UpdateRestLet2_6
      • Naming (needs deprecation aliases to change):
        • openJPAToEclipseLink and javaee6/7/8 start lowercase.
        • BounceCastleFromJdk15OntoJdk18On is a typo (Y/bouncycastle-jdk18on.yml:19).
    • K9 YAML options at their default value (Drop YAML options set to their default value (K9 of #1330) #1354)

      Details
      • Options set to their default value:
        • acceptTransitive: false (Y/java-version-17.yml:375,399)
        • recursive: false (Y/bouncycastle-jdk18on.yml:318, Y/ibm-java.yml:116, Y/jakarta-ee-9.yml:905; at :158 keep the comment and drop the option)
        • addParentheses: true (Y/java-best-practices.yml:47)
      • Deprecated option: onlyIfVersionsMatch: true (Y/jaxb-apis.yml:43,47,138).
      • Version selector: newVersion: 1.18.* → 1.18.x (Y/lombok.yml:63).
    • K10 Move tests to their recipe's package, and name them after it (Move tests to their recipe's package and name them after it (K10 of #1330) #1358)

      Details
      • Wrong package:
        • T/javaee/AddTableGeneratorTest and HttpSessionInvalidateTest → javax
        • T/javaee/ApacheDefaultProviderTest and ServletIsRequestedSessionIdFromURLTest → javaee8
        • T/javaee/OpenJPAPersistenceProviderTest → javaee7
        • T/lang/MigrateZipErrorToZipExceptionTest → root
        • T/maven/MigrateJaxwsMavenPluginTest → javax
      • Named after a different recipe than they run:
        • T/guava/NoGuavaPredicatesEqualToTest and NoGuavaSetsFilterTest run NoGuava
        • T/jakarta/JakartaXmlBindRuntimeTest
        • T/jakarta/UpdateAnnotationAttributeJavaxToJakartaTest runs JakartaEE10
        • T/UpgradeSpringBootForJava25Test
        • T/util/MigrateInflaterDeflaterToAutoCloseableTest
        • T/lang/UseThreadsOrExecutorsTest runs FindVirtualThreadOpportunities
        • T/lang/var/UseVarForObjectsTest
        • T/lombok/log/ConvertToLogAnnotationTest
      • Loaded via Environment.builder()/scanRuntimeClasspath instead of recipeFromResources: UseVarKeywordTest, JavaLangAPIsTest, PreferJavaUtilOptionalTest (which also has two @DocumentExamples).
    • K11 Stale @Disabled / @ExpectedToFail and demo tests (Resolve stale @Disabled and demo tests (K11 of #1330) #1353)

      Details
      • T/lang/var/UseVarForObjectsTest:313: Skip var replacement when the type cannot fully be resolved #608 was closed by skipping static methods, so turn this into a no-change test.
      • T/lang/UseTextBlocksTest:659 textBlockDemo: delete it.
      • T/lang/UseVarKeywordTest:216 and UseVarForObjectsTest:118: convert to no-change tests.
      • T/lombok/LombokOnXToOnX_Test:188: "Parser bug with @__ syntax". Re-check against the current parser.
      • T/lang/UseTextBlocksTest:971: redundant expectedCyclesThatMakeChanges(0).
    • K12 Remove deprecated recipes at the next major

      Details
      • M/ChangeMethodInvocationReturnType.java (fix D8 first)
      • M/ReplaceStringLiteralValue.java
      • M/RemoveIllegalSemicolons.java (after A5)
      • M/maven/UpdateMavenProjectPropertyJavaVersion.java
      • M/maven/UseMavenCompilerPluginReleaseConfiguration.java
  2. timtebeek commented on Oct 24, 2026

    @timtebeek
    MemberAuthor

    Fixes for the items in "Start here" are up for review:

    Two corrections from working on them:

    • B1 is less severe than described. Inside UpgradeToJava17, EnableLombokAnnotationProcessor runs first and already adds a Lombok processor path, so the build only broke when AddLombokMapstructBinding ran on its own. In the composite, the old behaviour mostly left the Lombok path at 1.18.40 instead of the project's version.
    • C1's second-null-check case only reproduces when both checks are on the switch subject. With the second check on another variable, the chain never reached three cases.

    One new finding for B7: when EnableLombokAnnotationProcessor creates <annotationProcessorPaths>, any other processors that javac used to pick up from the classpath (for example hibernate-jpamodelgen) stop running as well. Not addressed in #1335.

  3. moved this to Backlog in OpenRewriteon Oct 24, 2026
  4. melloware commented on Oct 29, 2026

    @melloware
    Contributor

    @timtebeek i posted on the Discord channel and got no response. I am having the hardest time using the Code Genome repo and i have a proper token and key but still getting 403's trying to run recipes. Can you have someone in support monitor the Discord channel?

  5. timtebeek commented on Oct 29, 2026

    @timtebeek
    MemberAuthor

    Sorry to hear! We're definitely more active on Slack if that's an option for you; in short you'll need a token to download the Apache licensed modules; we do not provide free downloads of the MSAL modules on our infra, as we found these were often used well beyond what the license allows. They remain source available, and within the terms (not sold as a service for instance), you can build these modules and run the recipes all the same. It's a few more steps unfortunately. The order can be seen on https://openrewrite.github.io/rewrite-recipe-bom/ , with build instructions on https://docs.openrewrite.org/reference/building-openrewrite-from-source. 🙏🏻
    You're the second one today expressing difficulty despite familiarity with the project, so we're also taking another look at our docs & process. 🤞🏻

  6. melloware commented on Oct 29, 2026

    @melloware
    Contributor

    thanks @timtebeek yeah the docs make it sound like just get a token change settings.xml and it will just work and it definitely does not for the JDK25 Recipe which is where I got the instructions in the first place.

  7. RojanFrancis commented on Nov 4, 2026

    @RojanFrancis

    Hi @timtebeek, I've opened fixes for two items on this list: C5 (ThreadStopUnsupported) in #1411 and C3 (SwitchCaseAssignmentsToSwitchExpression) in #1413. Both are ready for review once CI is approved. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions