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

Open
#1,330 6 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
30/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
java
Domain
tooling

Research direction

This is a large triage umbrella covering 205 Java recipe classes and 292 YAML recipes; read the item list and pick a single self-contained item by ID (e.g. F1 or B2). Recipes live under src/main/java/org/openrewrite/java/migrate/, declarative YAML under src/main/resources/META-INF/rewrite/, and tests under src/test/java/org/openrewrite/java/migrate/. Items in section A need a maintainer decision first, so comment before starting. Done means a fix plus a realistic probe test merged with the item ID referenced in the PR.

Written by the indexing model from the issue text.

Description

good first issue

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 (#250); see D1/D2. It also brings UseEnumSetOf ("too opinionated", #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 (#1340, #1343)

    Details
    • Y/java-version-21.yml:50: RemoveIllegalSemicolons is a deprecated wrapper of RemoveExtraSemicolons, which :32 already runs. Fixed in #1340.
    • Y/jakarta-faces-41.yml:29: Faces2xMigrationToJakartaFaces3x is already reached via :30, so it is reached four times inside JakartaEE11. Fixed in #1340.
    • Y/java-version-8.yml:34-35: Thread.destroy() removal duplicates ThreadStopDestroy (Y/java-version-11.yml:233-234) and sits before the JDK 11 removal. Fixed in #1340.
    • Y/java-version-11.yml:125-128: jaxb2-maven-plugin 2.5.x is bumped again by AddJaxbAPIDependencies (Y/jaxb-apis.yml:139-142). Fixed in #1340.
    • Y/jakarta-ee-10.yml:332+: the seven ChangeType javax.servlet.* entries in RemovalsServletJakarta10 are dead inside the composite, and half-migrate when run standalone. Fixed in #1343.
  • 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 (#1335)

    Details
    • Where: M/AddMapstructAnnotationProcessorPath.java:74-80, reached from Y/java-version-17.yml:355-381.
    • Problem (CONFIRMED): when Lombok is a plain dependency and there is no <annotationProcessorPaths>, the recipe creates one holding only lombok-mapstruct-binding and mapstruct-processor. Once paths are explicit, javac stops discovering processors on the classpath, so Lombok no longer runs and every @Getter/@Builder caller fails after UpgradeToJava17. T/AddLombokMapstructBindingTest.java:203-275 expects this output.
    • Fix: when creating the list, also add org.projectlombok:lombok at its resolved version. Otherwise only add to an existing list.
    • Also (SUSPECTED, :63-68): mapstructVersion is global, so the first module scanned wins. Key it per project. See also #1319 and #1119.
  • 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 (#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: #1246 and #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
    • EnableLombokAnnotationProcessor (Y/java-version-25.yml:187-192, SUSPECTED): the Gradle version: "[1.18.40)" has no comma and is probably written verbatim, so Gradle can't resolve it; only Maven is tested. Also onlyIfUsing: lombok.* misses lombok.extern..* and lombok.experimental..*.
    • LombokBestPractices (Y/lombok.yml:42-44, SUSPECTED): ExcludeDependency org.projectlombok:lombok also excludes it from modules without a direct Lombok dependency, which is exactly the shape of T/lombok/LombokBestPracticesTest.java:131-222. Modules that use Lombok then fail to compile.
    • Maven-only: the scope change and exclusion (Y/lombok.yml:34-44) skip Gradle.
    • Multi-module: EnableLombokAnnotationProcessor changes no pom when only one child of an aggregator uses Lombok. The root cause is openrewrite/rewrite#7850. See #1119.
  • B8 onlyIfUsing globs (#1338, #1343)

    Details
    • Y/jspecify.yml:170 (CONFIRMED): MigrateFromMicrometerAnnotations has onlyIfUsing: org.springframework.lang.*ull*, copied from the Spring recipe. Micrometer users don't get jspecify; Spring users get an unused dependency. It should be io.micrometer.core.lang.*ull*. Fixed in #1338.
    • Y/jakarta-ee-9.yml (SUSPECTED): javax.servlet.* (:862), javax.ws.rs.core.* (:1908) and javax.mail.* (:1867) miss subpackages. The other 27 entries use ..*. Fixed in #1343.
  • 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 #1314, and #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 (#1336)

    Details
    • Where: M/lang/IfElseIfConstructToSwitch.java, in UpgradeToJava21.
    • Null check on another variable (CONFIRMED, :127-144,201-213): a leading null check on a different variable still becomes case null of the switch. nullCaseInSwitch only feeds the case count. A second null check overwrites the first, and the first branch's body is lost. Probe: if (other == null) a(); else if (o instanceof Integer i) b(); else if (o instanceof Long l) c(); else d();.
    • Retargeted break (CONFIRMED, :171-175): only return is rejected. An unlabeled break now exits the new switch instead of the enclosing loop.
    • Dominance (CONFIRMED): instanceof CharSequence before instanceof String produces a dominated label, which is a compile error.
    • Related: #1305 (comments dropped) and #776.
  • C2 NullCheckAsSwitchCase drops the else branch (#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: #698.
  • C8 JavadocToMarkdownDocComment comments out code on the same line (#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 (#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
    • Where: M/util/UseListOf.java, UseSetOf.java and UseMapOf.java, reached via JavaUtilAPIs → JavaBestPractices (A3).
    • Duplicates (CONFIRMED): Set.of/Map.of throw IllegalArgumentException on duplicate elements or keys, where HashSet deduplicates and HashMap keeps the last write (UseSetOf:74-85,93-119, UseMapOf:72-99).
    • Runtime nulls (CONFIRMED): only literal null is excluded, so list.add(map.get(k)) throws an NPE after migration (UseListOf:301-322, UseSetOf:245-266, UseMapOf:323-345).
    • Anonymous {{ }} form (CONFIRMED):
      • The receiver isn't checked, so other.add(x) is folded into the new list and the write to other is lost (UseListOf:116-124, UseSetOf:100-107, UseMapOf:120-130).
      • ArrayList<String> l = new ArrayList<>() {{..}} becomes = List.of(..), which doesn't compile.
      • add(null) throws.
    • Related: #1307 and #1310.
  • 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: #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
    • ReplaceSystemOutWithIOPrint (M/io/ReplaceSystemOutWithIOPrint.java:43-61, CONFIRMED):

      • IO only has print(Object)/println(Object), so println(char[]) now prints [C@...
      • There's no Java 25 gate, so Kotlin modules capped at 24 get IO.println, which doesn't exist there.

      See also A1 and #1304.

    • MigrateStringReaderToReaderOf (CONFIRMED, :53-104): every nested new StringReader is rewritten, so wrap(new StringReader(s)) with wrap(StringReader), or a Supplier<StringReader> lambda, no longer compiles.

  • D5 java.net recipes (#1341)

    Details
    • MigrateMulticastSocketSetTTLToSetTimeToLive (CONFIRMED, :51-53): the recipe uses Byte.valueOf(b).intValue(), which sign-extends, while the JDK uses Byte.toUnsignedInt. So setTTL((byte) 200) becomes setTimeToLive(-56), which throws IAE. The test at T/net/..SetTTL..Test.java:59 asserts the wrong output. Fixed in #1341.
    • MigrateMulticastSocketGetTTLToGetTimeToLive (CONFIRMED, Y/java-net-apis.yml:33-43): byte ttl = s.getTTL() becomes an int call, which doesn't compile. Fixed in #1341.
    • URLConstructorToURICreate (CONFIRMED, :76-83): the literal is validated after .trim(), but URI doesn't trim, so new URL(" https://x") throws at runtime after migration.
    • URLConstructorsToNewURI (unwired, #467): it introduces a checked URISyntaxException, encodes ? in the file part, and uses URL.of (Java 20) without a gate.
    • URL encoder/decoder: the switch from the platform charset to UTF-8 is undocumented.
  • 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 (#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 #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 (#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: #436, and #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 #434.

F. Lombok

  • F1 UseLombokSetter deletes setters that do more than assign the parameter (#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
    • Where: M/lombok/LombokUtils.java:180-182,214-216.
    • Problem (CONFIRMED): Lombok generates setFoo for a primitive boolean isFoo, but the recipes expect setIsFoo. UseLombokSetter deletes setIsFoo and callers break; AdoptLombokSetterMethodNames renames a correct setFoo to setIsFoo.
    • Fix: mirror Lombok's toSetterName.
    • Related: #879.
  • 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() (#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 (#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 (#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 #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 (#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()

  • I1 Keep method types in sync (#1351)

    Details
    • IteratorNext:60-64, StreamFindFirst:61-65 (D3).
    • MigrateCollectionsUnmodifiableList:52-68 (keeps the zero-argument List.of() type).
    • AbstractNoGuavaImmutableOf:124-128.
    • PreferJavaUtilOptionalOrElseNull (withSimpleName).
    • NoGuavaPredicatesInstanceOf:55-71: hand-built member reference with null types.
    • LombokValueToRecord:229-236,334-340.
    • ReplaceAWTGetPeerMethod (D8).
    • StringFormatted:77-80: picks any member named formatted.
    • Six more withSimpleName renames in M/util, M/net and M/logging (see the triage notes).
  • I2 Remove TypeValidation.none() where it hides recipe bugs (#1348)

    Details
    • Scope: 17 none() and 5 partial relaxations.
    • Hides a recipe bug:
      • T/DontOverfetchDtoTest:33. Fixed in #1348.
      • T/guava/NoGuavaPredicatesInstanceOfTest:41. Fixed in #1348.
      • T/logging/MigrateLoggerGlobalToGetGlobalTest:39. This was stale, not a recipe bug. Fixed in #1348.
    • Stale: T/util/UseLocaleOfTest:40, whose FIXME is about "not Java 19". Fixed in #1348.
    • Gate on the JRE instead: T/util/MigrateStringReaderToReaderOfTest:35 and T/io/ReplaceSystemOutWithIOPrintTest:34 should use @EnabledForJreRange(min = JAVA_25), since CI runs 25. Fixed in #1348; this exposed a wrong IO.println(Object) type in ReplaceSystemOutWithIOPrint.
    • Fix the classpath:
      • T/lombok/LombokValueToRecordTest:426 needs jackson-annotations. Fixed in #1348.
      • T/lombok/LombokValToFinalVarTest:198-311 needs the Lombok classpath. Fixed in #1348.
    • Legitimate (missing-type subject), so keep: IfElseIfConstructToSwitchTest:1072,1103, LombokOnXToOnX_Test:33, the JAXB/annotation no-change tests in T/jakarta, and AddJaxbApiForUnattributedImportsTest.

J. Preconditions & declarative

  • J1 Add preconditions (#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 (#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 #1334.

    Fixed in #1346.

  • J3 Replace isAcceptable with preconditions (#1337)

    Details
    • M/lang/ExtractExplicitConstructorInvocationArguments.java:67: use not(KotlinFileChecker) and not(GroovyFileChecker), as SwitchCaseReturnsToSwitchExpression does.
  • J4 Make it declarative (#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.
Dominant language
Java
Stars
162
Forks
139
Avg merge
10h 28m
Merged PRs (30d)
121

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from openrewrite/rewrite-migrate-java

All issues in openrewrite/rewrite-migrate-java

Similar issues

More Java issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.