Recipe quality triage: fixes, wiring and polish (A1–L3)
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 30/100
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
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
mainatac6f4827and 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:
- G1:
HttpSessionInvalidatereplacessession.invalidate()withrequest.logout()in everyJavaxMigrationToJakartarun. - B1:
AddLombokMapstructBindingcreatesannotationProcessorPathswithout Lombok inUpgradeToJava17. - F1:
UseLombokSetterdeletes setters that transform their argument. - E1 and D1:
Set.of/Map.ofthrow on duplicates and nulls where the replaced code didn't. - C1–C3: the Java 21 switch restyles drop branches or change control flow.
- B3: Docker tags become ones that don't exist.
- G7: Faces 3 text replacements stop the Faces 4 XML updates from running.
- C5:
ThreadStopUnsupportedleaves 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).MigrateMainMethodToInstanceMainwas deliberately kept inJavaBestPracticesonly (Y/java-best-practices.yml:34-36).Java8toJava11→UpdateLombokToJava11(Y/lombok.yml:79-83): rewritesvaltofinal var, and writeslombok.val.flagUsage = errorinto everylombok.config, althoughvalstill works on Lombok 1.18.
- Fix: move them to
JavaBestPractices(or aJava21BestPractices), and keep only what the JDK upgrade requires.
- Problem: these change style or semantics in code that compiles fine on the new JDK. Each also has its own bugs (see C/D).
-
A2 Vendor switches and scope changes reached by every Jakarta EE / Java 11+ migration
Details
-
Provider switches:
JavaxMigrationToJakarta(Y/jakarta-ee-9.yml:32) includesjavaee8→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) andHttpSessionInvalidate(G1). - OpenJPA → EclipseLink in
-
IBMSemeru(Y/ibm-java.yml:33-34→:133-180): includesRemovedJavaXMLWSModuleProvidedandRemovedJaxBModuleProvided, whose own display names say "in WebSphere Liberty applications". They set the JAXB/JAX-WS API toprovided, so a non-Liberty app loses JAXB at runtime. SUSPECTED. -
AddCommonAnnotationsDependencies(Y/add-common-annotations-dependencies.yml:42-45): setsjakarta.annotation-apitoprovidedunconditionally 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/earpackaging.
-
-
A3
JavaBestPractices→JavaUtilAPIsre-enables recipes the Java 11 chain disabledDetails
- Where:
Y/java-best-practices.yml:64. - Problem:
JavaUtilAPIsbrings back the null-unsafe collection factories that were left out of the Java 11 chain for null safety (#250); see D1/D2. It also bringsUseEnumSetOf("too opinionated", #958) withconvertEmptySetat its defaulttrue, and re-runs 10 Java recipes already in theUpgradeToJava25chain (Java recipes are not Singleton-guarded). - Conflict:
RemoveFinalizerFromZip(Y/java-util-apis.yml:39) deletes the zipfinalize()calls, whileRemovedZipFinalizeMethods(Y/java-version-17.yml:206-228) renames them toend()/close(). Pick one. - Fix: list only the
JavaUtilAPIsentries that aren't already in the chain, and decide on the null-unsafe ones.
- Where:
-
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.StringRulesRecipesandlang.UseStringIsEmptyRecipe→JavaBestPractices.search.FindLocaleDateTimeFormats→UpgradeToJava21, as Java 25 does withFindCompletableFutureDefaultExecutor.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, pins3.0.0-alpha-1, untested, and adds the plugin to every pom despite its javadoc.AddSuppressionForIllegalReflectionWarningsPlugin: configuresmaven-jar-pluginonwar/ear, where it doesn't run. It is a no-op with no test.
- Duplicate:
io.ReplaceFileInOrOutputStreamFinalizeWithCloseduplicates YAMLRemovedFileIOFinalizeMethods(Y/java-version-17.yml:290-307). Delete one. - Unreferenced YAML building blocks:
ComIntelliJAnnotationsToOrgJetbrainsAnnotations(add toMigrateToJSpecifybeforeMigrateFromJetbrainsAnnotations),WasDevMvnChangeParentArtifactId,AddJaxbDependenciesWithoutRuntime.
- Wire:
-
A5 Remove duplicate and dead entries (#1340, #1343)
Details
Y/java-version-21.yml:50:RemoveIllegalSemicolonsis a deprecated wrapper ofRemoveExtraSemicolons, which:32already runs. Fixed in #1340.Y/jakarta-faces-41.yml:29:Faces2xMigrationToJakartaFaces3xis already reached via:30, so it is reached four times insideJakartaEE11. Fixed in #1340.Y/java-version-8.yml:34-35:Thread.destroy()removal duplicatesThreadStopDestroy(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-plugin2.5.x is bumped again byAddJaxbAPIDependencies(Y/jaxb-apis.yml:139-142). Fixed in #1340.Y/jakarta-ee-10.yml:332+: the sevenChangeType javax.servlet.*entries inRemovalsServletJakarta10are dead inside the composite, and half-migrate when run standalone. Fixed in #1343.
-
A6 Placement questions
Details
UpgradeToJava6bumps JaCoCo (Y/java-version-6.yml:29). Move it toUpgradePluginsForJava11+.UpgradeToJava21callsSetupJavaUpgradeJavaVersionwith no option (Y/java-version-21.yml:40), so it relies on the upstream default being 21. Move it toUpgradePluginsForJava21withminimumJavaMajorVersion: 21.MigrateFastjsonForJakarta10(Y/jakarta-ee-10.yml:56,812) is a fastjson1 → 2 library migration insideJakartaEE10(see also G8).UpgradeJavaForJetty12(Y/jakarta-ee-9.yml:1679-1686) bumps the build to Java 17 inside an EE 9 migration, without theUpgradeToJava17source 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) andY/java-version-21.yml:82-94(guava29.0, byte-buddy, modelmapper). EnableLombokAnnotationProcessoris defined inY/java-version-25.yml:172but first used inUpgradeToJava17. Move it tojava-version-17.ymlorlombok.yml.UpdateAnnotationAttributeJavaxToJakarta(Y/jakarta-ee-10.yml:39-44) fixes"javax.jms.Queue"string attributes that go stale at EE 9. Move it toJavaxMigrationToJakarta.- The Kotlin floor
UpgradeJavaVersion(24)(Y/java-version-25.yml:315-334) also moves Dockerfiles, CI images and.sdkmanrcto the EOL non-LTS 24. Give the floor a build-only variant. SUSPECTED.
B. Build files, dependencies & multi-module
-
B1
AddLombokMapstructBindingcreatesannotationProcessorPathswithout Lombok (#1335)Details
- Where:
M/AddMapstructAnnotationProcessorPath.java:74-80, reached fromY/java-version-17.yml:355-381. - Problem (CONFIRMED): when Lombok is a plain dependency and there is no
<annotationProcessorPaths>, the recipe creates one holding onlylombok-mapstruct-bindingandmapstruct-processor. Once paths are explicit, javac stops discovering processors on the classpath, so Lombok no longer runs and every@Getter/@Buildercaller fails afterUpgradeToJava17.T/AddLombokMapstructBindingTest.java:203-275expects this output. - Fix: when creating the list, also add
org.projectlombok:lombokat its resolved version. Otherwise only add to an existing list. - Also (SUSPECTED,
:63-68):mapstructVersionis global, so the first module scanned wins. Key it per project. See also #1319 and #1119.
- Where:
-
B2
UpgradeKotlinJvmTargetVersion: targets beyond the Kotlin compiler, and missed shapesDetails
- Where:
M/UpgradeKotlinJvmTargetVersion.java:106-110,139-148,248-276, reached through the Kotlin-cappedUpgradeJavaVersion(24)inY/java-version-25.yml:315-334. - Too-high target (CONFIRMED path): Kotlin 1.9 supports at most
jvmTarget21, and 2.0/2.1 are also below 24. Mavenkotlincfails with "Unknown JVM target version", and Gradle KTS can't resolveJvmTarget.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(..), GroovycompilerOptions { },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/.ktsource, not just build scripts (:72-89).
- Where:
-
B3
UpgradeDockerImageVersionproduces 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 fromopenjdktoeclipse-temurinkeeps OpenJDK-only variants (-slim,-slim-bullseye,-oraclelinux8,-buster), which Temurin doesn't publish.
- Where:
-
B4
AddJaxbRuntime: swaps runtimes in projects that don't use JAXB, possibly downgrading Jakarta JAXB 4Details
- Where:
M/javax/AddJaxbRuntime.java:186-206,233-247, reached viaY/jaxb-apis.yml:37fromJava8toJava11, and so from every Java 17/21/25 upgrade. - Problem (CONFIRMED for the unconditional swap): the
ChangeDependencydelegates run on every build file, even whenusesJaxbis false, socom.sun.xml.bind:jaxb-implbecomesorg.glassfish.jaxb:jaxb-runtime:2.3.x. SUSPECTED:ChangeDependencyGroupIdAndArtifactIdhas no downgrade guard, so a Jakartajaxb-impl:4.0.xends up on the javax 2.3 runtime. - Also:
usesJaxbis one repo-wide flag (:77,113); key it by project.- The description (
:66-70) namescompileOnly/provided, but the code addsruntimeOnly/runtime.
- Probe: a pom with
com.sun.xml.bind:jaxb-impl:4.0.5andjakarta.xml.bind-api:4.0.2, run throughJava8toJava11. - Related: #1246 and #1314.
- Where:
-
B5 Repo-wide flags in scanning recipes
Details
M/jakarta/HasJettyDependency.java:46-67,87(SUSPECTED): one globalAtomicBoolean. One module with an embedded Jetty 9 test server moves all modules to Java 17 and everyorg.eclipse.jetty:*to 12.0.x (Y/jakarta-ee-9.yml:1667-1685).M/search/ModuleHasKotlinSource.java:72-93withY/java-version-25.yml:359-366(SUSPECTED): a parent pom has no.ktfiles, soinvertMarkinglets it move to release 25, and Kotlin 1.x children inherit that. Probe: a parent withmaven.compiler.release=17and a Kotlin 1.9 child.M/jakarta/HasNoJakartaAnnotations.java:51-65withY/jakarta-ee-9.yml:1552-1564(SUSPECTED): it scans before the javax → jakarta rename, so ajavax.annotation.PostConstructuser counts as having "no Jakarta annotations", andjakarta.annotation-apiis removed on Boot 2.x.- See also B1 (
mapstructVersion) and B4 (usesJaxb).
-
B6
AddSurefireFailsafeArgLineadds@{argLine}without definingargLineDetails
- 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-121claims otherwise. - Fix: reuse
AddMockitoJavaAgentToMavenSurefirePlugin:92-100,137-139, which handles exactly this case.
- Where:
-
B7 Lombok dependency handling
Details
EnableLombokAnnotationProcessor(Y/java-version-25.yml:187-192, SUSPECTED): the Gradleversion: "[1.18.40)"has no comma and is probably written verbatim, so Gradle can't resolve it; only Maven is tested. AlsoonlyIfUsing: lombok.*misseslombok.extern..*andlombok.experimental..*.LombokBestPractices(Y/lombok.yml:42-44, SUSPECTED):ExcludeDependency org.projectlombok:lombokalso excludes it from modules without a direct Lombok dependency, which is exactly the shape ofT/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:
EnableLombokAnnotationProcessorchanges no pom when only one child of an aggregator uses Lombok. The root cause is openrewrite/rewrite#7850. See #1119.
-
B8
onlyIfUsingglobs (#1338, #1343)Details
Y/jspecify.yml:170(CONFIRMED):MigrateFromMicrometerAnnotationshasonlyIfUsing: org.springframework.lang.*ull*, copied from the Spring recipe. Micrometer users don't get jspecify; Spring users get an unused dependency. It should beio.micrometer.core.lang.*ull*. Fixed in #1338.Y/jakarta-ee-9.yml(SUSPECTED):javax.servlet.*(:862),javax.ws.rs.core.*(:1908) andjavax.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,AddApiDependencyForImportsandRetainJaxbApiForArquillianRecorder(Y/jakarta-ee-9.yml:1019-1051): Gradle projects never get a Jakarta JAXB runtime. -
M/jakarta/DoesNotHaveProvidedApiDependency.java:53-70(SUSPECTED):- Maven: misses
providedinherited from the parent'sdependencyManagement. - Gradle: checks only
compileOnly, notprovidedCompile.
Either way, a compile-scope API gets added to WARs.
- Maven: misses
-
M/javax/AddJaxwsRuntime.java:103-108: the Gradle runtime goes intocompileOnly+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
MigrateGraalVMResourceConfigloses resourcesDetails
- Where:
M/MigrateGraalVMResourceConfig.java, inUpgradeToJava25(Y/java-version-25.yml:45). - Quoted patterns (CONFIRMED,
:320-381):\Q...\E, whichnative-image-agentemits for every literal resource, becomes a glob that matches nothing. - Excludes (CONFIRMED,
:115,139-157):excludesare 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": ..} ]intoresource-config.jsonmay not be what GraalVM's legacy parser accepts there. CheckLegacyResourceConfigurationParserbefore fixing the rest.
- Where:
-
B11 Smaller build-file items
Details
M/UpgradeUnmodeledMavenJavaVersion.java:46-52(SUSPECTED): bumps fixture poms undersrc/test/resources,src/itandarchetype-resources.M/UpdateSdkMan.java:92-94,134(SUSPECTED): only the root.sdkmanrcis 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: wrongcase null,breakretargeted, dominated labels (#1336)Details
- Where:
M/lang/IfElseIfConstructToSwitch.java, inUpgradeToJava21. - Null check on another variable (CONFIRMED,
:127-144,201-213): a leading null check on a different variable still becomescase nullof the switch.nullCaseInSwitchonly 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): onlyreturnis rejected. An unlabeledbreaknow exits the new switch instead of the enclosing loop. - Dominance (CONFIRMED):
instanceof CharSequencebeforeinstanceof Stringproduces a dominated label, which is a compile error. - Related: #1305 (comments dropped) and #776.
- Where:
-
C2
NullCheckAsSwitchCasedrops theelsebranch (#1331)Details
- Where:
M/lang/NullCheckAsSwitchCase.java:69-98andM/lang/NullCheck.java:43-46, inUpgradeToJava21. - Problem (CONFIRMED):
if (s == null) { a(); } else { b(); } switch (s) {..}removes theifand keeps onlya()ascase null, sob()is gone. Fix: requirewhenNotNull() == 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.
- Where:
-
C3
SwitchCaseAssignmentsToSwitchExpression: labeledbreak, selector readsDetails
- Where:
M/lang/SwitchCaseAssignmentsToSwitchExpression.java, inUpgradeToJava21. - Labeled break (CONFIRMED,
:176):x = 5; break outer;becomesyield 5, so the loop is no longer left. RequiregetLabel() == null. - Selector reads the variable (CONFIRMED,
:71-87,93-170):String s = "a"; switch (s) { case "a": s = "b"; ..}becomesString s = switch (s) {..}, which fails with "might not have been initialized". Withint x = i; switch (i++), the default branch reads the incrementedi. - Comments (SUSPECTED,
:111-119): comments in arrow blocks and in thebreakprefix are lost.
- Where:
-
C4 Other switch recipes
Details
SwitchCaseEnumGuardToLabel(CONFIRMED,:109-114): any identifier becomes a label, socase Suit s when s == fav ->becomescase fav ->. Require an enum-constant field type.RefineSwitchCases(CONFIRMED,:135-150): a non-block then/else part (return;,break;, a nestedif) 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 fallbackreturn/throwis deleted, so new constants now throwMatchException. return;cases yield an invalidreturn switch.
- For an exhaustive enum switch without
-
C5
ThreadStopUnsupportedreplaces the call with athrowanywhereDetails
- Where:
M/lang/ThreadStopUnsupported.java:52-55. - Problem (CONFIRMED): on Java 21+,
t.stop()becomesthrow 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
UsesMethodprecondition, andUsesJavaVersionruns again for every match. - The comment is a hand-built
TextComment; use theCommentstrait (K5).
- There's no
- Where:
-
C6
MigrateMainMethodToInstanceMainon interfaces, abstract classes, enums and recordsDetails
- Where:
M/lang/MigrateMainMethodToInstanceMain.java:90-151. - Problem (CONFIRMED): the class kind and
abstractaren't checked.interface Main { static void main(String[] a) {} }becomesvoid 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 callSpringApplication.run(App.class, args).
- Where:
-
C7
UseVar*recipesDetails
UseVarForTypeCast(CONFIRMED,:50-59,84-89): bypassesDeclarationCheck.isVarApplicable. Fields of anonymous classes getvar, and so doesString s[] = (String[]) o; neither compiles. Fold it intoDeclarationCheck.UseVarForObject(CONFIRMED,:59-73):Object o = "a"; o = 1;narrows the type and breaks the reassignment. Mirror theisReassignedguard fromUseVarForGenericsConstructors:85.- SUSPECTED (
DeclarationCheck.java:444-466):- Kotlin isn't excluded.
- TYPE_USE annotations (for example JSpecify
@Nullable) stay in front ofvar, which javac rejects. - A
J.AnnotatedTypeloses its annotation.
- Related: #698.
-
C8
JavadocToMarkdownDocCommentcomments out code on the same line (#1345)Details
- Where:
M/lang/JavadocToMarkdownDocComment.java:98-102. - Problem (CONFIRMED):
enum Color { /** Red */ RED, /** Green */ GREEN }becomes/// Redfollowed byRED, ...on the same line, so the code ends up inside the comment. - Also (SUSPECTED,
:813-825):<T>becomes a raw<T>, which renders as an invisible HTML tag.
- Where:
-
C9
DanglingDocCommentToBlockCommentnever converges on banner comments (#1364)Details
- Where:
M/DanglingDocCommentToBlockComment.java:61-68, inUpgradeToJava25. - 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**.
- Where:
-
C10 Smaller
langitemsDetails
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 infinallyor resources. Constructor exceptions are now wrapped inInvocationTargetException, which the description doesn't mention.MigrateProcessWaitForDuration(SUSPECTED,:70-79):waitFor(Long.MAX_VALUE, DAYS)now throwsArithmeticException.MigrateSecurityManagerMulticast(SUSPECTED,:52-57): the dropped second argument may have side effects. It could also be a declarativeDeleteMethodArgument.ExtractExplicitConstructorInvocationArguments(SUSPECTED,:122-131,207-216): a nested type gets anOuter.Innerimport, and on a simple-name clash it binds to the wrong type. "Strictly behavior-preserving" over-claims.LiteralOrVariablehas no usages.
D. JDK API migrations (util, net, io, sql, logging, root)
-
D1
UseListOf/UseSetOf/UseMapOf: duplicates, nulls, foreign receiversDetails
- Where:
M/util/UseListOf.java,UseSetOf.javaandUseMapOf.java, reached viaJavaUtilAPIs→JavaBestPractices(A3). - Duplicates (CONFIRMED):
Set.of/Map.ofthrowIllegalArgumentExceptionon duplicate elements or keys, whereHashSetdeduplicates andHashMapkeeps the last write (UseSetOf:74-85,93-119,UseMapOf:72-99). - Runtime nulls (CONFIRMED): only literal
nullis excluded, solist.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 tootheris 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.
- The receiver isn't checked, so
- Related: #1307 and #1310.
- Where:
-
D2
Collections.*→List.of/Set.of/Map.of: undocumented semantic changesDetails
-
MigrateCollectionsEmpty{List,Map,Set}(:53-60):sort,removeIfandclearwere 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): anynew X(Arrays.asList(..))is accepted, soTreeSet/LinkedHashSetlose their order, duplicates throw, and{{ add(..) }}bodies are dropped. CONFIRMED. -
MigrateCollectionsUnmodifiableList(:52-68):unmodifiableList(asList(arr))was a live view ofarrand 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)threwIndexOutOfBoundsException, butgetFirst()throwsNoSuchElementException. Document it.StreamFindFirst(CONFIRMED,:57-58):- It NPEs on
stream()with an implicitthis. isOfClassType(SequencedCollection)is an exact match, soList/Dequereceivers never change. UseisAssignableTo.
- It NPEs on
- Stale types:
IteratorNext:60-64andStreamFindFirst:61-65clone the type ofiterator()/stream(), sogetFirst()returnsIterator<E>/Stream<E>. - YAML: the
SequencedCollectionrenames (Y/java-version-21.yml:97-117) have no Java 21 gate and lackmatchOverrides.
-
D4
ReplaceSystemOutWithIOPrintandMigrateStringReaderToReaderOfDetails
-
ReplaceSystemOutWithIOPrint(M/io/ReplaceSystemOutWithIOPrint.java:43-61, CONFIRMED):IOonly hasprint(Object)/println(Object), soprintln(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 nestednew StringReaderis rewritten, sowrap(new StringReader(s))withwrap(StringReader), or aSupplier<StringReader>lambda, no longer compiles.
-
-
D5
java.netrecipes (#1341)Details
MigrateMulticastSocketSetTTLToSetTimeToLive(CONFIRMED,:51-53): the recipe usesByte.valueOf(b).intValue(), which sign-extends, while the JDK usesByte.toUnsignedInt. SosetTTL((byte) 200)becomessetTimeToLive(-56), which throws IAE. The test atT/net/..SetTTL..Test.java:59asserts the wrong output. Fixed in #1341.MigrateMulticastSocketGetTTLToGetTimeToLive(CONFIRMED,Y/java-net-apis.yml:33-43):byte ttl = s.getTTL()becomes anintcall, which doesn't compile. Fixed in #1341.URLConstructorToURICreate(CONFIRMED,:76-83): the literal is validated after.trim(), butURIdoesn't trim, sonew URL(" https://x")throws at runtime after migration.URLConstructorsToNewURI(unwired, #467): it introduces a checkedURISyntaxException, encodes?in the file part, and usesURL.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, becomesnew PrintWriter(null), which is ambiguous and doesn't compile.MigrateLoggerLogrbToUseResourceBundle(CONFIRMED,:51-60):ResourceBundle.getBundle(name)throwsMissingResourceException, where theStringoverload logged without localization.MigrateGetLoggingMXBeanToGetPlatformMXBean(CONFIRMED,:54-61): theChangeTypeonly runs in calling files, so standalone runs break call sites in other files. Move it togetRecipeList().MigrateLoggerGlobalToGetGlobal(SUSPECTED,:43): the template has a stray;, and the test relaxesmethodInvocations. Replace it withChangeStaticFieldToMethod.UseJavaUtilBase64(CONFIRMED JDK behaviour,:89-110): with the defaults,decodeBuffer→getDecoder()throws on line-wrapped input, andencodeBufferoutput changes for inputs over 57 bytes. Map these to the MIME coder.
-
D7 Exception-type swaps narrow the catch and break
throwsDetails
- Where:
M/ArrayStoreExceptionToTypeNotPresentException.java:52-58andM/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.
ChangeTypealso rewritesthrow 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.
- Where:
-
D8 Root-package API recipes (#1366)
Details
-
ReferenceCloneMethod:69(CONFIRMED): passes the reference itself as the referent instead ofref.get(). ForWeakReference<String>the output doesn't compile. -
MXBeanRule:84-110(CONFIRMED): applies to any*MBeanclass once one non-public interface exists in the file.public interface FooMBeanbecomespublic public, and an abstract*MBeanclass losesabstract. -
ReplaceComSunAWTUtilitiesMethods:121,127-128(CONFIRMED):- An unchecked
(J.FieldAccess)cast throws on a static-imported constant. - The
superresult is discarded. - The template has no closing
).
The tests only use
com.test.*patterns, never the shipped defaults. - An unchecked
-
ReplaceAWTGetPeerMethod:76-78,112-114(CONFIRMED): resolves to the deprecated same-packageChangeMethodInvocationReturnType, whose visitor is a no-op, so the return type is never fixed. -
JREThrowableFinalMethods:93-107(SUSPECTED): also renames calls declared onjava.lang.Throwableitself. -
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
RemovedSecurityManagerMethodsandRemovedSubjectMethodsDetails
-
M/RemovedSecurityManagerMethods.java:46-50(CONFIRMED):boolean b = sm.checkTopLevelWindow(w)becomes avoidcall.AllPermissionis 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)ordoAs(Subject, ..). - Renaming alone wouldn't compile anyway (
callAstakes aCallable). - The display name has a stray backtick.
Delete it, or write a Java recipe.
- The zero-argument patterns never match
-
-
D10
SimplifyMicrometerMeterTagsproduces broken codeDetails
- 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.
- Where:
E. Guava
-
E1
NoGuavaJava11:ImmutableSet/ImmutableMap→Set.of/Map.ofchange runtime behaviourDetails
- Where:
M/guava/AbstractNoGuavaImmutableOf.java:93-130andY/no-guava.yml:104-109. - Problem (CONFIRMED, JDK spec):
Set.of(a, b)throws IAE whena.equals(b); Guava deduplicates.- The iteration order of
Set.of/Map.of/copyOfis unspecified and randomized per JVM, while Guava keeps insertion order. JSON key order,toStringand test assertions change. contains(null)/get(null)throw an NPE on all JDK immutables, includingList.of.
- Fix: make the Set and Map conversions opt-in, or limit them to distinct compile-time constants, and document the null-query difference.
- Where:
-
E2
AbstractNoGuavaImmutableOfcrashes and wrong targetsDetails
- Varargs (CONFIRMED,
:202-225):getParameterTypes().get(index)throwsIndexOutOfBoundsExceptionwhen the call is a vararg argument, for exampleString.format("%s %s", "a", ImmutableList.of("b"))or SLF4Jlog.info(..). - Lambdas (CONFIRMED,
:193-201): areturninside a lambda is checked against the enclosing method's return type. - Select branch (CONFIRMED,
:208-209):ImmutableSet.of(..).asList()becomesSet.of(..).asList(). - SUSPECTED:
- The rewritten call keeps the
ImmutableList.ofmethod type. ImmutableList.of(arr)gives a one-element list, whileList.of(arr)spreads the array.convertReturnType=trueretypes variables that still use Guava-only API.
- The rewritten call keeps the
- Metadata:
getShortType(:71-73) renders descriptions as "Replaces.common.collect.ImmutableList.of(..)" (visible inrecipes.csv:147-152).
- Varargs (CONFIRMED,
-
E3
NoGuavaemits Java 9–16 APIs with no version gateDetails
- 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) andoptional.stream()(:89-107).- Picnic
InputStreamRulesRecipes(Y/no-guava.yml:86):transferTo,readAllBytesand others.
- Optional on Java 8 (CONFIRMED,
Y/no-guava.yml:184-186):or(Optional)is renamed toorElse(Optional), becausePreferJavaUtilOptionalOrSupplieronly runs on 9+. Narrow the pattern toor(java.lang.Object). - Fix: move these into
NoGuavaJava11/NoGuavaJava21, or addUsesJavaVersionpreconditions.
- Problem (CONFIRMED): run on a Java 8 project, these produce code that doesn't compile:
-
E4
PreferCharComparetargetsjava.lang.Char(#1333)Details
- Where:
Y/no-guava.yml:306-318. - Problem (CONFIRMED):
java.lang.Chardoesn't exist, soChars.compare(a, b)becomesChar.compare(a, b)when run standalone. InsideNoGuava,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 ofInlineGuavaMethods(PreferInteger/Long/ShortCompare,PreferMath*Exact). Add a test.
- Where:
-
E5
Function/Supplier/Optional/Predicatetype swaps leave Guava consumers brokenDetails
-
Unconditional swaps (CONFIRMED,
Y/no-guava.yml:145-157,160-192,211-223): theChangeTypes apply unconditionally.- Consumers break:
Lists.transform(l, F),Maps.transformValues,Futures.transformandSuppliers.memoizenow getjava.util.functiontypes. - Producers break:
Iterables.tryFindandEnums.getIfPresentstill return a GuavaOptional.
Reuse the "file uses such an API" exclusion from
NoGuavaPredicate. - Consumers break:
-
Predicates (CONFIRMED):
NoGuavaPredicatesAndOr:95-101,NoGuavaPredicatesEqualTo:70-78andPredicates.notproduce ajava.util.function.Predicateregardless ofNoGuavaPredicate's exclusion.NoGuavaPredicatesAndOrTest:53-59andNoGuavaFunctionsComposeTest:61-79expect output that doesn't compile.NoGuavaPredicatesEqualTo:70-71builds its type witness fromtoString(), givingPredicate.<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)becomesList<int[]>, andLongs.asList(1, 2)becomesList<Integer>.NoGuavaCreateTempDir:65-77(CONFIRMED): calls inside lambdas, or inside thecatch/finallyof a try that catchesIOException, are converted and leave an unhandledIOException.NoGuavaSetsFilter:59(CONFIRMED):toCollection(TreeSet::new)drops the comparator. Also see@ExpectedToFailatT/guava/NoGuavaSetsFilterTest.java:106.NoMapsAndSetsWithExpectedSize:64-91(CONFIRMED): gated on Java 19, but it emitsnew HashMap<>(n)(capacity) instead ofHashMap.newHashMap(n).NoGuavaRefaster:76-79(CONFIRMED): static-importsString.valueOf, which is shadowed inside enums.PreferJavaUtilOptional(CONFIRMED):get()now throwsNoSuchElementExceptioninstead ofIllegalStateException, and staticOptional.toJavaUtil(x)isn't handled.NoGuavaDirectExecutor:50-56(SUSPECTED): no guard for.execute(r)orvarusage.PreferJavaStringJoinVisitor:64-69(SUSPECTED): the template lacks its closing).JoinerNPEs on null elements, whileString.joinprints"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 ownequals(Object), the static import ofObjects.equalsis shadowed, soequals(a, b)doesn't compile. See #434.
F. Lombok
-
F1
UseLombokSetterdeletes setters that do more than assign the parameter (#1332)Details
- Where:
M/lombok/LombokUtils.java:122-178. - Problem (CONFIRMED):
isSetterchecks the name, owner and parameter type, but never the right-hand side.this.name = name.trim(),this.items = new ArrayList<>(items)andthis.x = 5are all replaced by a plain@Setter, so the logic is lost silently. - Fix: require the assigned value to be a
J.Identifierreferring to the method's own parameter.
- Where:
-
F2
boolean isFoofields: wrong setter nameDetails
- Where:
M/lombok/LombokUtils.java:180-182,214-216. - Problem (CONFIRMED): Lombok generates
setFoofor a primitiveboolean isFoo, but the recipes expectsetIsFoo.UseLombokSetterdeletessetIsFooand callers break;AdoptLombokSetterMethodNamesrenames a correctsetFootosetIsFoo. - Fix: mirror Lombok's
toSetterName. - Related: #879.
- Where:
-
F3
AdoptLombok{Getter,Setter}MethodNamesrename methods that aren't accessorsDetails
- 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; }becomesgetVERSION(), andownerName() { return owner.name; }becomesgetName(). - Fix: reuse the owner, non-static and
this.-only checks fromisGetter/isSetter.
- Where:
-
F4 Constructor recipes drop annotations
Details
UseRequiredArgsConstructor/UseAllArgsConstructor(CONFIRMED,M/lombok/LombokUtils.java:282-335): parameter annotations andthrowsare ignored, soS(@Qualifier("primary") Foo foo)becomes@RequiredArgsConstructorand the qualifier is lost.UseNoArgsConstructor(CONFIRMED,:47-70): drops constructor annotations (@Inject,@JsonCreator,@Deprecated), comments in the body (A() { // for JPA }), andthrows.- SUSPECTED:
- Varargs parameters (
:313) produce anA(String[])constructor. - Non-Lombok
@NonNullannotations (:235-258) change Lombok's set of required fields, so callers break.
- Varargs parameters (
-
F5
FieldAnnotatorkeeps an existing@Getter/@Setterand its access levelDetails
- 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.
- Where:
-
F6
LombokBestPracticesbreaks modules without Lombok, and Java 8 projectsDetails
- No dependency check (CONFIRMED,
Y/lombok.yml:17-48): there's noModuleHasDependency org.projectlombok:lombokprecondition and nothing adds the dependency, so non-Lombok modules get Lombok annotations that don't compile. The standaloneUseLombok*andUse*ArgsConstructorrecipes behave the same. - Java 8 (CONFIRMED,
Y/lombok.yml:27,M/lombok/LombokValToFinalVar.java:53-60):UpdateLombokToJava11runsval→final varwith no Java 10 gate. - Fix: add the dependency precondition and a
HasJavaVersiongate. See also A1 and B7.
- No dependency check (CONFIRMED,
-
F7
UseLombokLogAnnotations: nested classes and name clashesDetails
- 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 tologwithout a clash check, soLOGGER.info(log)with alogparameter breaks. - SUSPECTED:
lombok.configkeys are never read:lombok.log.fieldName,lombok.accessors.*,lombok.getter.noIsPrefix.- The plain
JavaIsoVisitors also hit Groovy and Kotlin. synchronizedgetters,throwsclauses and Javadoc on removed methods are dropped.
- Nested classes (CONFIRMED,
-
F8
LombokValueToRecordDetails
extends(CONFIRMED,:110-117):@Value class A extends Bbecomesrecord A(..) extends B.- Arrays (CONFIRMED,
:190-202,315-332): array fields lose Lombok's deepequals/hashCode/toString. toStringformat: withoutuseExactToString,A(a=1)becomesA[a=1].- Stale types (
:229-236,334-340): call sites renamed tox()keep the method type, and the record's class type is replaced by an emptyShallowClass. - Description: should warn that getter call sites and
toStringchange.
G. Jakarta EE / Java EE / JPA / JSpecify
-
G1
HttpSessionInvalidatereplacessession.invalidate()withrequest.logout()(#1334)Details
- Where:
M/javax/HttpSessionInvalidate.java:51-75, reached by everyJavaxMigrationToJakartarun viajavaee8→javaee7→javaee6(Y/java-ee-6.yml:28). - Problem (CONFIRMED): the description says "Add …
logout", but the code replacesinvalidate().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 checkedServletException.T/javaee/HttpSessionInvalidateTest.java:39-55encodes 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.
- NPE when there's no enclosing method declaration, or its type is missing (
- Fix: keep
invalidate()and addlogout()before it only whenServletExceptionis handled, or make the recipe search-only.
- Where:
-
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)becomesTABLEwithout 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): addspublic X() {}to entities that have no constructor, and acts on outer classes of nested entities.RemoveEmbeddableId:113-116(SUSPECTED): unchecked(JavaType.Class)cast.
-
G3
RemoveBeanIsNullablecleans up the whole fileDetails
- Where:
M/jakarta/RemoveBeanIsNullable.java:47-51. - Problem (CONFIRMED):
RemoveUnusedLocalVariablesandSimplifyConstantIfBranchExecutionrun on the whole compilation unit, which deletes unrelated code. Also:foo(bean.isNullable())becomesfoo( false).- A select with side effects is dropped.
- Where:
-
G4
UpdateGetRealPathmisses nested calls (#1339)Details
- Where:
M/jakarta/UpdateGetRealPath.java:41-51. - Problem (CONFIRMED): there's no
supercall, soPaths.get(req.getRealPath("/"))is never migrated and fails on Servlet 6. - Also (SUSPECTED): with no select, as in a
HttpServletRequestWrappersubclass,nullis passed to#{any()}.
- Where:
-
G5
BeanDiscovery,JpaCachePropertiesandAddStaticVariableOnProducerSessionBeanDetails
BeanDiscovery:67-72(CONFIRMED, test encodes it): an explicitbean-discovery-mode="none"or"annotated"is overwritten with"all". Keep an explicit mode.JpaCacheProperties:93-102(CONFIRMED):versionis 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 getsNONE, including Hibernate apps, which silently disables@Cacheable.AddStaticVariableOnProducerSessionBean:81-101(CONFIRMED dead): it only matchesjakartatypes, but runs injavaee7onjavaxcode. If it did fire, a static@Produces @PersistenceContextfield would break.
-
G6
WsWsocServerContainerDeprecationnever renames insideJakartaEE10Details
- Where:
Y/jakarta-ee-10.yml:201-214, run afterJavaxMigrationToJakarta(:27vs:34). - Problem (CONFIRMED): the method pattern names the
javax.servletparameters, which have already been renamed. TheChangeTypestill applies, givingServerContainer.doUpgrade(..), which doesn't exist. The standalone test passes. - Fix: use
doUpgrade(..). Even then,upgradeHttpToWebSocketis an instance method, so the change needs a TODO comment or a Java recipe.
- Where:
-
G7 Faces 3
FindAndReplaceturns XML into plain text, so the Faces 4 updates are skippedDetails
- Where:
Y/jakarta-faces-3.yml:126-129(faces-config),:154-157(taglib) and:182-189(web-fragment), versusY/jakarta-faces-4.yml:141,161,181. - Problem (CONFIRMED by tracing): any file containing
javax.becomes aPlainText, and the laterChangeTagAttributeversion andschemaLocationupdates skip it, so these files stay at 3.0/5.0 underJakartaEE10/11. - Fix:
web.xmlwas already moved toChangeTagValue; do the same for these three. - Probe:
JakartaEE10on a 2.3 faces-config with<el-resolver>javax.el.BeanELResolver</el-resolver>.
- Where:
-
G8
MigrateFastjsonForJakarta10: swapped enums and a non-recursive package change (#1342)Details
- Where:
Y/jakarta-ee-10.yml:866-874. - Problem (CONFIRMED):
SerializerFeaturemaps toJSONReader.Featureandparser.FeaturetoJSONWriter.Feature, the wrong way round.ChangePackage com.alibaba.fastjsonisn't recursive, soannotation.JSONFieldkeeps the old package after the dependency swap.
- Also: it has no tests. See also A6.
- Where:
-
G9 CDI and JSF recipes
Details
AddScopeToInjectedClass/AnnotateTypesVisitor(CONFIRMED, unwired):- It adds
@Dependentto classes that already have a scope (a CDIDefinitionException), and to interfaces and abstract classes. - It throws
ClassCastExceptionon@Inject T t.
- It adds
UpdateManagedBeanToNamed:56-79(SUSPECTED): an unscoped@ManagedBeanis request-scoped, but an unscoped@Namedis@Dependent.eager = trueis dropped. Add@RequestScoped. See also #1023.JacksonProviderOverrides:45-46: targets_configForWriting(ObjectMapper, Annotation[]), which nojackson-jaxrs-basesince 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-305javax.annotation.Nullableimports addjakarta.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 aJ.AnnotatedTypeand is skipped. The laterChangeTypethen flips "nullable array" into "nullable elements".Y/jspecify.yml:114-115(CONFIRMED viajavap): the comment claims Jakarta's@Nullabletargets 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):ChangeTypekeeps them, and none compile on JSpecify: JetBrains@NotNull("msg"), Micronaut@Nullable(inherited = ..), javax@Nonnull(when = ..).when = MAYBEreally means nullable. @ParametersAreNonnullByDefault→@NullMarked(:90-93, SUSPECTED):@NullMarkedalso covers returns and fields.- See also B8.
H. Search recipes & data tables
-
H1
AboutJavaVersionreports the target as the source version (#1365)Details
- Where:
M/search/AboutJavaVersion.java:75. - Problem (CONFIRMED): the source-compatibility column uses
getMajorReleaseVersion(); it should usegetMajorVersion(). The test uses equal source and target, so it can't catch this. - Also: the
transient Set seenSourceSetsfield (:39) is shared across runs.
- Where:
-
H2 DTO and javax search recipes
Details
-
DontOverfetchDto(CONFIRMED,M/DontOverfetchDto.java:60-145):- Signatures change without updating callers.
- Uses like
feed(dog)/return dogare left dangling. dog.getName()can become an unboundname.- The test's
TypeValidation.none()hides an inlinedjava.lang.StringFQN.
Restrict it to private methods, make it search-only, or delete it.
-
FindDataUsedOnDto:51-57(CONFIRMED): nosupercall, sodto.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-onlyjavaxAPIs (Cipher,javax.sql,javax.swing). -
FindJavaVersion:57/PlanJavaMigration:51:preVisitwithoutstopAfterPreVisit()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-argumentList.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 namedformatted.- Six more
withSimpleNamerenames inM/util,M/netandM/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:35andT/io/ReplaceSystemOutWithIOPrintTest:34should use@EnabledForJreRange(min = JAVA_25), since CI runs 25. Fixed in #1348; this exposed a wrongIO.println(Object)type inReplaceSystemOutWithIOPrint. - Fix the classpath:
T/lombok/LombokValueToRecordTest:426needsjackson-annotations. Fixed in #1348.T/lombok/LombokValToFinalVarTest:198-311needs the Lombok classpath. Fixed in #1348.
- Legitimate (missing-type subject), so keep:
IfElseIfConstructToSwitchTest:1072,1103,LombokOnXToOnX_Test:33, the JAXB/annotation no-change tests inT/jakarta, andAddJaxbApiForUnattributedImportsTest.
- Scope: 17
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(UsesTypefrom the option). - Edit visitor only:
AddTransientAnnotationToEntity:85. - Lombok: the five
Use*Logrecipes (UsesMethod(factoryMethodPattern)). - Java version:
MigrateProcessWaitForDuration:44(addUsesMethod),ReplaceSystemOutWithIOPrint,OptionalStreamRecipe,JavaConcurrentAPIs(Java 9).
- Root package:
-
J2
UsesType→UsesMethod(MATCHER)reusing the visitor's own matcher (#1346)Details
net/URLConstructorsToNewURI:46net/URLConstructorToURICreate:55nio/file/RedundantUtf8Charset:60util/RemoveFinalizerFromZip:55-57JREThrowableFinalMethods:63:UsesType<>("java.lang.Throwable", true)matches nearly every file.guava/AbstractNoGuavaImmutableOf:91HttpSessionInvalidate:44-47: alreadyUsesMethodsince #1334.
Fixed in #1346.
-
J3 Replace
isAcceptablewith preconditions (#1337)Details
M/lang/ExtractExplicitConstructorInvocationArguments.java:67: usenot(KotlinFileChecker)andnot(GroovyFileChecker), asSwitchCaseReturnsToSwitchExpressiondoes.
-
J4 Make it declarative (#1350)
Details
M/net/MigrateHttpURLConnectionHttpServerErrorToHttpInternalError.java→ReplaceConstantWithAnotherConstant. This also removes an unconditionaldoAfterVisit.M/logging/MigrateLoggerGlobalToGetGlobal.java→ChangeStaticFieldToMethod.M/lang/MigrateSecurityManagerMulticast.java→DeleteMethodArgument, if side effects are acceptable (C10).- Guava Refaster candidates: the
Lists/Maps/Setsfactory 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
Singletongoes before polishingDetails
- Status: 257
Singletonpreconditions; 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.
- Status: 257
-
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:
RemovedSecurityManagerMethodsAddJDeprScanPluginAddSuppressionForIllegalReflectionWarningsPluginPlanJavaMigrationInlineGuavaMethods(79 entries)PreferChar/Integer/Long/ShortComparePreferMath*ExactPreferJavaUtilFunction/SupplierPreferJavaNioCharsetStandardCharsetsopenJPAToEclipseLink
- Highest-value gaps:
- the confirmed bugs in E4, B8, G6, G7, G8 and D9
- Lombok on Gradle (B7)
UpgradeToJava25on a Kotlin 1.x module (B2, D4)- the Java 25
AccessController/RemoveSecurityPolicy/RemoveSecurityManagerrecipes, which also runUnnecessaryCatch/UnnecessaryThrowsfile-wide UpdateApacheCommonsEmailDependenciesNoGuavawithjavaVersion(8)(E3)
- Not testing the shipped defaults:
ReplaceComSunAWTUtilitiesMethodsTest:30andReplaceAWTGetPeerMethodTest:30usecom.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.jsonformat accepts globs? - Kotlin (B2): confirm the maximum
jvmTargetper Kotlin version. - Docker (B3): which
eclipse-temurinvariants 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.
- GraalVM (B10): which
- Dominant language
- Java
- Stars
- 162
- Forks
- 139
- Avg merge
- 10h 28m
- Merged PRs (30d)
- 121
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from openrewrite/rewrite-migrate-java
-
bug
Difficulty 3/5 1-2 days Newbie friendliness 72/100
openrewrite/rewrite-migrate-java#1383 ·
Maintainers usually reply within 1 day
-
UseMapOf: Skip refactoring large maps to Map.ofEntries to prevent javac compiler hangs (add maxEntries option)Possibly taken @Yuren0312 claimed this 17 days ago. Openbug
Difficulty 3/5 1-2 days Newbie friendliness 55/100
openrewrite/rewrite-migrate-java#1310 · 1 comment ·
Maintainers usually reply within 1 day
-
Recipe UseMapOf generates Map.of() with nullable values or duplicate keys, causing runtime NullPointerExceptionPossibly taken @henriquejsza claimed this 12 days ago. Openbug
Difficulty 4/5 3-5 days Newbie friendliness 55/100
openrewrite/rewrite-migrate-java#1307 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 4/5 3-5 days Newbie friendliness 68/100
openrewrite/rewrite-migrate-java#1246 ·
Maintainers usually reply within 1 day
-
Feedback from a 152k-line Java 8 to 25 migration: the silent behavioural changes that cost the most, and which have no recipePossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 5/5 Over a week Newbie friendliness 32/100
openrewrite/rewrite-migrate-java#1227 · 4 comments · 1 reaction ·
Maintainers usually reply within 1 day
All issues in openrewrite/rewrite-migrate-java
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
aws/aws-emulator#30 · 1 comment ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 4 days
-
Bug: Minor Status: Triage
Difficulty 2/5 1-3 hours Newbie friendliness 63/100
GTNewHorizons/GT-New-Horizons-Modpack#27664 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 79/100
openrewrite/rewrite#9835 ·
Maintainers usually reply within 1 day
-
serviceability
Difficulty 1/5 Under an hour Newbie friendliness 65/100
OpenLiberty/open-liberty#36348 ·
Maintainers usually reply within 1 day