---
name: bump-java-8-to-11
description: Migrate a Maven or Gradle project one Java LTS step from Java 8 to Java 11: it must compile under JDK 11, conserve every previously-passing test, and raise the effective compiler target to 11, using only standard tools (JDKs, Maven or Gradle, OpenRewrite recipes from Maven Central; no project-specific scripts). Use for an 8→11 Java LTS bump.
---

# Bump Java 8 → 11

Migrate the project in your working directory from Java 8 to Java 11. goal: it compiles and passes its tests under JDK 11, conserving every test that passed under JDK 8, with the effective compiler target raised to 11 (a project that merely compiles under 11 but still targets 8 is not a bump). Work autonomously until done.

## Tools: standard only (JDKs 8 and 11, Maven or Gradle, OpenRewrite from Maven Central)
Three operations recur below; run each with the two JDKs, no project-specific scripts:

**first detect the build tool and use only it for every operation:** `pom.xml` present → Maven; otherwise (`build.gradle`/`build.gradle.kts`) → Gradle. **never introduce the other build system**: do not create a `pom.xml` in a Gradle project (or a `build.gradle` in a Maven one). The build/test gate compiles whatever build file is present, so adding the wrong one silently breaks dependency resolution (deps declared in the project's real build tool show up as `package … does not exist`).
- **compile under JDK N**: Maven: `JAVA_HOME=<jdkN> mvn -B -ntp -DskipTests compile`; Gradle: `./gradlew -Dorg.gradle.java.home=<jdkN> compileJava` (also `compileKotlin`/`compileTestJava`).
- **test under JDK N**: Maven: `JAVA_HOME=<jdkN> mvn -B -ntp test`; Gradle: `./gradlew -Dorg.gradle.java.home=<jdkN> test`.
- **apply the OpenRewrite program**: write `rewrite.yml` (below), then run it **under JDK 8 with the Java-11 recipe artifacts**:
  - Maven: `JAVA_HOME=<jdk8> mvn -B -ntp -U -Denforcer.skip=true org.openrewrite.maven:rewrite-maven-plugin:6.40.0:run -Drewrite.configLocation=$(pwd)/rewrite.yml -Drewrite.activeRecipes=com.bjv.Bump -Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-migrate-java:3.35.0,org.openrewrite.recipe:rewrite-spring:6.31.0,org.openrewrite.recipe:rewrite-java-dependencies:1.55.0`, the **absolute** `configLocation` (`$(pwd)/rewrite.yml`) is required, else multi-module submodules report `Recipe(s) not found`.
  - Gradle: apply the OpenRewrite plugin through an init-script with `rewrite-migrate-java:3.35.0` / `rewrite-spring:6.31.0` / `rewrite-java-dependencies:1.55.0` on the `rewrite` configuration, then run `rewriteRun`.

critical. never time-box these builds. Cold Gradle/Maven runs download distributions + the OpenRewrite jars and take minutes; let them finish. An apply that was cut off means the recipe was not applied (tests may still pass at the old Java level, but the bytecode-target check fails). After applying, confirm BUILD SUCCESS and that the build files actually changed.

## How to work (graded on a correct migration that conserves tests: nothing else counts if a test is lost or the bytecode isn't really at 11)
- Prefer the off-the-shelf transforms: the unparametrized meta-recipe + setting the Java version to 11 + (only if needed) the pinned Gradle wrapper are the clean path. Use them first.
- **On Gradle, land the target with a direct build-file edit; the reward follows the target, not the tool.** Setting the target to 11 is a free hop-fixed intent (no penalty), and the combined gate credits the bump only once the effective bytecode target actually reaches 11. `UpgradeJavaVersion` often leaves a Gradle toolchain (`JavaLanguageVersion.of(N)`) or `sourceCompatibility`/`targetCompatibility` untouched, and the `rewrite-gradle-plugin` init-script can fail to resolve offline or clash with the repo's Gradle version, so a run that loops on it while the target never lands earns nothing (it scores `FAIL_target_not_bumped` with `edits 0`). The dependable path to the reward is to set the target yourself: `JavaLanguageVersion.of(<11)` to `of(11)`, plus any `sourceCompatibility`/`targetCompatibility`/`options.release`/`JavaVersion.VERSION_*` below 11, across the root and every module (`allprojects`/`subprojects`). Keep OpenRewrite for the heavier transforms (Spring, javax->jakarta, dependency floors); if `rewriteRun` will not resolve, the direct edit has already secured the target. (On Maven the `rewrite-maven-plugin` route is reliable; the direct edit is `<release>11</release>` or `maven.compiler.release`.)
- Treat every manual edit and every project-specific dependency/plugin change as a liability, more code to get right, more risk. Make the fewest changes that genuinely work; reach for a hand edit or a project-specific recipe only when a real wall demands it.
- Never touch or weaken test code (see forbidden).

## Proactive step: verify the Java target actually landed (Gradle/Maven pin the recipe leaves at 8; silent FAIL_target_not_bumped)
`UpgradeJavaVersion` frequently does not touch the build file's target pin, so the build compiles cleanly to old (Java-8, major 52) bytecode with no error and silently scores `FAIL_target_not_bumped`. After applying the recipe, grep every build file for a pin still `< 11` and hand-bump it before the JDK-11 build. Use a simple grep (do not improvise a `find -exec`, a malformed one wastes turns and can get you stuck): `grep -rnE 'JavaLanguageVersion.of|VERSION_1[._]?8|sourceCompat|targetCompat|options.release|<source>|<target>|<release>|maven.compiler|jvmTarget' . 2>/dev/null`. Bump every pin you find (a project can have two): Gradle `java { toolchain { languageVersion = JavaLanguageVersion.of(8) } }` → `of(11)`; `sourceCompatibility`/`targetCompatibility = JavaVersion.VERSION_1_8` (or `'1.8'`) → `VERSION_11`/`'11'`; `options.release = 8` → `11`; Maven `<source>1.8`/`<target>1.8`/`<release>8` / `maven.compiler.source` → `11`; Kotlin `jvmTarget "1.8"` → `"11"`. This applies to plain-Java projects too (not just Kotlin). Counts as a free hop-fixed intent. (Proven: jmini/asciidoctorj-dynamic-include, plain-Java, sole pin `java { toolchain { JavaLanguageVersion.of(8) } }` the recipe left untouched: `of(8)`→`of(11)` flipped FAIL_target_not_bumped `target 8` → VERDICT PASS `target 11`, 35 tests, 0 edits.)

## Proactive step: Spring Boot line (run BEFORE the first JDK-11 build; gated on a declared dependency, not on an error)
If the build declares Spring Boot (grep the build files for `org.springframework.boot`), raise the line with the OpenRewrite recipe rather than by hand. The recipe moves the whole managed set together and migrates the code with it, which is what a hand-written version pin cannot do: pinning one member of a managed family (a bare `jackson-databind`, `logback-classic` or `netty-handler` version) leaves its siblings behind and the tests die with `NoClassDefFoundError` on a class from that same family. Measured on a fixed web profile, the line you land on sets the dependency-vulnerability count the gate rewards: Spring Boot 2.7.18 carries 42 critical+high, 3.3.x carries 22, 3.4.x carries 17, 3.5.14 carries 12, and 3.5.15 and 3.5.16 carry none.
- Include the recipe **only when the project actually resolves an `org.springframework` artifact**. It is not a free add-on: every `UpgradeSpringBoot_*` carries `SpringBoot2JUnit4to5Migration`, which rewrites JUnit 4 to JUnit 5 and removes `junit:junit` from the build. On a project with no Spring that is pure damage: measured on openrewrite/jgit, which has no Spring and 3754 JUnit 4 tests, the recipe deleted `junit:junit`, left JUnit-4-only helpers such as `org.junit.rules.TestRule` unresolvable, and the whole suite was lost. Add it to the START-HERE recipe list yourself, only after the grep confirms Spring is present.
- Pick the recipe by the line the project is **already on**, not by the JDK you are targeting. A project on Boot 2.x gets `org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7`; a project already on Boot 3.x gets `org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_5`. Sending a Boot 2.x project straight to 3.5 drags javax to jakarta plus Spring Security 6 in one step: measured on mosip/commons (Boot 2.0.2) it rewrote 1297 files and lost 1916 of 2409 tests. That jump belongs in the reflect loop, attempted only when a Spring wall actually blocks the build, and kept only if the tests survive.
- After the recipe runs, compile under the SOURCE jdk before going any further. OpenRewrite edits sources and poms independently and never type-checks the result, so it reports BUILD SUCCESS even when it has emitted code that cannot compile. If that compile fails, the recipe is the cause and its edits are what you fix.
- Do not jump to Spring Boot 4.x for security: measured 4.0.0 through 4.0.5 score worse than 3.5.6 on the same profile, and 4.x moves Jackson to the `tools.jackson` coordinates, which is an API break you would pay for in lost tests.
Counts as a **free hop-fixed intent**, like setting the target: the recipe run is not a manual edit.

## Proactive step: JaCoCo floor (run BEFORE the first JDK-11 build; gated on a declared plugin, not on an error)
If the build declares JaCoCo (grep the build files for `org.jacoco`), floor it to **0.8.15** before the first JDK-11 build. Never wait for the error. Measured on this hop: the lowest JaCoCo that actually instruments class-file major 55 is **0.8.2** (anything below writes no execution data), and 0.8.15 works on every hop, so pinning the newest 0.8 patch is both correct and uniform. The agent has to read the bytecode it instruments, so one too old for class-file major 55 fails, and it does not always fail loudly: when it cannot instrument it may leave the build green and surface later as an assertion difference in a test that reads instrumented output, with jacoco named nowhere in the log. The structural trigger (the project declares `org.jacoco`) is unambiguous, which is why this is proactive.
- Apply it with `org.openrewrite.java.migrate.jacoco.UpgradeJaCoCo`, which moves every `org.jacoco` artifact and the `jacoco-maven-plugin` to the newest 0.8 patch. That recipe is **not** reachable from `UpgradePluginsForJava11`, so add it to the START-HERE recipe list yourself; running the java-plugin recipes alone leaves JaCoCo untouched.
- Check where the plugin is actually declared before assuming a root-level change reached it. A version written in a module's own pom (`<version>` inside that module's `<build><plugins>`) is not overridable from the root, so a property bump or a root `pluginManagement` entry silently does nothing and you get the agent on one version and the report on another. Gradle declares it as `jacoco { toolVersion }`, which the Maven recipe cannot reach at all: edit that directly.
Counts as a **free hop-fixed intent**, like setting the target.

## Proactive step: Lombok floor (run BEFORE the first JDK-11 build; gated on what the build resolves, not on an error)
If the build resolves Lombok, floor it to **1.18.30** before the first JDK-11 build. Never wait for the error. Lombok before 1.18.x reads javac internals the JDK 9+ module system closed off, and it does not fail with a version string or even its own name on the error line: it dies as `Fatal error compiling: java.lang.ExceptionInInitializerError: com.sun.tools.javac.code.TypeTags`, with lombok appearing only in an `Illegal reflective access by lombok.javac.apt.LombokProcessor` warning several lines earlier. javac dies before type-checking anything, so a fail-fast reactor aborts during an early module and loses every test in every later module before the run reaches the reactive back-stop row. (Proven: apache/shardingsphere, root `<lombok.version>1.16.4</lombok.version>`, aborted an 18-module reactor at module 2 and lost all 43275 tests; 1.16.4 -> 1.18.30 clears it. buckett/canvas-api-client-feign, whose pom mentions lombok zero times and which resolves 1.16.12 transitively from feign-form 2.1.0: a dependencyManagement floor at 1.18.30 plus javax.annotation-api took it from 0 to 903 tests green.) JDK 11 does not need `maven.compiler.proc=full`; that is a JDK-23+ requirement, see the 21->25 skill.
Gate this on what the build resolves, not only on what it declares: besides grepping the build files, run `mvn -B -ntp dependency:tree -Dincludes=org.projectlombok:lombok` (gradle: `./gradlew dependencies --configuration compileClasspath | grep lombok`). Lombok often arrives transitively at compile scope into a project whose build files never mention it (proven: `io.github.openfeign.form:feign-form:2.1.0` drags in lombok 1.16.12), and javac still discovers its processor through ServiceLoader and runs it. Then put the floor where it actually wins: a literal `<version>` or a module-local property is rewritten by `org.openrewrite.java.dependencies.UpgradeDependencyVersion` {groupId: org.projectlombok, artifactId: lombok, newVersion: 1.18.30}, but check every module, a child that pins its own version is not reachable from the root. If no version exists anywhere and `spring-boot-starter-parent` is the module's actual `<parent>`, set `<lombok.version>1.18.30</lombok.version>` in that pom's properties. If instead `spring-boot-dependencies` arrives as a `<scope>import</scope>` entry, the property override is a silent no-op, an imported BOM interpolates its own properties before the import ever sees yours; add a direct `<dependencyManagement>` entry for `org.projectlombok:lombok` at 1.18.30 in the aggregator pom, a locally managed version beats every imported BOM. The same entry is the fix for transitive-only Lombok, since there is no declaration to rewrite. A green recipe run is not proof the floor landed: `UpgradeDependencyVersion` reports BUILD SUCCESS and changes zero files when there is no version string to rewrite, exactly the import-BOM and transitive cases, so confirm with `dependency:tree` that 1.18.30 is what resolves before building under the new JDK. (Verified at 17->21 on LuckyKuang/leaning-demo, Boot 3.1.1 imported, no lombok version declared: the property override left 1.18.28 resolving; a direct dependencyManagement entry landed 1.18.30 and the 36-module reactor went green.)

## Start here: write `rewrite.yml`, then apply it
```
type: specs.openrewrite.org/v1beta/recipe
name: com.bjv.Bump
recipeList:
  - org.openrewrite.java.migrate.Java8toJava11
  - org.openrewrite.java.migrate.UpgradeJavaVersion:
      version: 11
  # PROACTIVE (include ONLY if the project RESOLVES Lombok: see the proactive step above):
  - org.openrewrite.java.dependencies.UpgradeDependencyVersion:
      groupId: org.projectlombok
      artifactId: lombok
      newVersion: 1.18.30
```
Then compile under JDK 11. If it compiles, test under JDK 11. Green tests are not done: you must also pass the **target gate** at the end of this skill. A build that compiles and conserves tests but still targets 8 scores `FAIL_target_not_bumped` and earns nothing.

## Reflect loop: if compile or test under 11 fails, read the error, fix the first wall, re-run (no iteration limit)
Keep going until it passes or you have exhausted real options.
Wall → fix:
- `package javax.xml.bind`/`javax.annotation.Generated`/`JAXBException` (EE modules removed in JDK 11): the `Java8toJava11` recipe already re-adds these (jaxb-api/jaxb-runtime/javax.activation/javax.annotation-api 2.3.1) and floors surefire to 2.22.2, trust it. only if it persists during annotation processing (`<annotationProcessorPaths>`): add jaxb-api + javax.annotation-api as `<path>` entries there too. Remove any leftover `--add-modules java.xml.bind`/`java.se.ee` flag.
- Obsolete/incompatible build plugin (animal-sniffer "requires ASM7", or one enforcing a Java-8 floor): add `org.openrewrite.maven.RemovePlugin` {groupId, artifactId} to rewrite.yml, apply again.
- Dependency too old for JDK 11 (lombok < 1.18.20, byte-buddy < 1.10, mockito < 3): add `org.openrewrite.java.dependencies.UpgradeDependencyVersion` {groupId, artifactId, newVersion}.
- Gradle wrapper floor: a wrapper too old for JDK 11 fails at Gradle startup, before any compile. Two symptoms, either means bump the wrapper: (a) `Could not determine java version from '11.x'` — a pre-5.0 wrapper (2.13, 4.x) cannot parse the JDK-11 version string and dies immediately; (b) a green `compileJava`/`compileTestJava` but `:test` fails with `post 0 lost N` because the wrapper's own ASM cannot read the freshly-compiled major-55 test bytecode (Gradle 4.x). Default: leave a Gradle 5.0–6.x wrapper as-is (it runs on JDK 11 fine); bump only on one of those two symptoms — do not skip it just because you are doing direct build-file edits rather than an OpenRewrite apply. When you bump, target **6.9** (not 7.x) via the `org.openrewrite.gradle.UpdateGradleWrapper` {version: "6.9"} recipe, or set gradle-wrapper.properties to gradle-6.9 by hand. never jump to Gradle 7.x. It removed `compile`/`testCompile` (breaks old build files: `Could not find method compile()`). never point distributionUrl at a `file://` path. If a build truly needs Gradle 7+, also rewrite `compile`→`implementation`/`testCompile`→`testImplementation`.
- Spring Boot 1.x can't run on 11: add `org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7`.
- runtime `ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1` from a `<clinit>` (Jadira etc. parsing `java.version` the old `1.x` way): bump the lib (Jadira → `7.0.0.CR1`); no JDK-11 release → bail.
- `package <internal> is not visible` (project uses java.awt.peer / sun.security.* / sun.misc.*): no clean recipe, hand-edit the build file to add `--add-exports=<module>/<package>=ALL-UNNAMED` to the compiler args (java.awt.peer→java.desktop, sun.security.*/sun.misc.*→java.base).
- Multi-module: a JDK bump is per-build, not per-module. `Dependency resolution is looking for a library compatible with JVM runtime version N, but 'project :X' is only compatible with M` = set the same target in every module (root `allprojects`/`subprojects`).
- A removed/changed JDK API in the project's own source: hand-edit the source minimally.
- **Kotlin project, `Could not find org.jetbrains.kotlin:kotlin-stdlib-jre8:<newver>`** (or `-jre7`): the `-jre8`/`-jre7` stdlib artifacts were renamed to `-jdk8`/`-jdk7` in Kotlin 1.2 and no longer publish for newer Kotlin. If you (or a recipe) raised `kotlinVersion` to >= 1.2, rename `kotlin-stdlib-jre8`->`kotlin-stdlib-jdk8` and `kotlin-stdlib-jre7`->`kotlin-stdlib-jdk7` in the build file.
- **`Unresolved reference: fastinfoset` / `import com.sun.xml.internal.fastinfoset...`** (project source imports a `com.sun.xml.internal.*` class, the JDK-bundled FastInfoset/JAXB internals, present in JDK 8 but removed in 11): rewrite the import to the external package (`com.sun.xml.internal.fastinfoset` -> `com.sun.xml.fastinfoset`) and add the external dep `com.sun.xml.fastinfoset:FastInfoset:1.2.18` (Gradle `implementation`/`compile`, Maven `<dependency>`). Same shape as any removed-internal-JDK-API wall.
- **Kotlin runtime `NoClassDefFoundError: kotlin/reflect/KotlinReflectionInternalError`** (often surfaces as `Could not initialize class <X>Kt` when a Kotlin/Jackson factory inits): a dependency jar was compiled against an old Kotlin (<= 1.1) and calls the top-level `kotlin.reflect.KotlinReflectionInternalError`, which was moved to `kotlin.reflect.jvm.internal` and deleted from the top level in newer kotlin-reflect, so it vanishes once `kotlinVersion` is raised. Find the stale lib (check each Kotlin dep's POM for `kotlin-reflect` 1.1.x: e.g. `com.natpryce:konfig:1.6.1.0` -> bump to `1.6.10.0`; an old `jackson-module-kotlin` like 2.8.x -> bump the whole jackson stack to ~2.12.7) and bump it to a build compiled against Kotlin >= 1.2.
- **Kotlin target not raised (`FAIL ... target` / bytecode still 8):** the Java `sourceCompatibility`/`UpgradeJavaVersion` recipe does not touch Kotlin. Raise `kotlinOptions.jvmTarget` (every `compileKotlin`/`compileTestKotlin` block) from `"1.8"` to `"11"` (or set `kotlin { jvmToolchain(11) }`). Kotlin 1.4+ accepts jvmTarget `"11"`. Cover all the pin forms: `kotlinOptions.jvmTarget = "1.8"` -> `"11"`; the typed `compilerOptions { jvmTarget.set(JvmTarget.JVM_1_8) }` / `jvmTarget = JvmTarget.JVM_1_8` -> `JvmTarget.JVM_11`; and `kotlin { jvmToolchain(8) }` -> `(11)`. **Pure-Kotlin Gradle projects often have no `kotlinOptions`/`jvmTarget` at all, the only pin is the Java toolchain `java { toolchain { languageVersion.set(JavaLanguageVersion.of(8)) } }`, from which the Kotlin plugin derives its jvmTarget; raise that `JavaLanguageVersion.of(8)` -> `of(11)` and the Kotlin bytecode follows (no separate jvmTarget needed, so no "Inconsistent JVM-target" error). If both a Java toolchain and an explicit Kotlin `jvmTarget` are present, raise both to 11 to match or you hit "Inconsistent JVM-target compatibility".** (Verified: featurevisor/featurevisor-kotlin, Gradle 8.2.1. Sole pin was `JavaLanguageVersion.of(8)`, no jvmTarget block; `of(11)` -> bytecode major 55, 6 tests conserved.)

- **`maven-surefire-plugin:<2.22 ... NullPointerException` (or `ArrayIndexOutOfBoundsException`) at the `test` goal:** surefire < 2.22 parses `java.version` the old `1.x` way and NPEs/AIOOBs the moment it runs under JDK 9+. The `Java8toJava11` recipe floors surefire to 2.22.2, but it does not override a version the project hard-pins in `<pluginManagement>` (e.g. `<artifactId>maven-surefire-plugin</artifactId><version>2.20.1</version>`), hand-bump that pinned `<version>` to `2.22.2` (a JDK-11-capable, offline-resolvable floor). Common in multi-module reactors: an old surefire pinned in the root POM NPEs the first module, aborting the whole reactor and wiping every test (`post 0 lost N`), fixing the pin lets the reactor complete. (Verified: jaxxy-rs/jaxxy 8->11, root POM pinned surefire 2.20.1; bumping to 2.22.2 cleared the `jaxxy-util` NPE.)
- **`Could not resolve dependencies ... org.openjfx:javafx.base:jar:11.0.0-SNAPSHOT was not found`:** an old `hibernate-validator` (pulled by spring-boot 1.x/2.0.x BOMs, e.g. 6.0.11.Final) has a `jdk11+` profile (`<jdk>[9,)</jdk>`/`[11,)`) that adds `org.openjfx:javafx.base:${java.version}`, under JDK 11 that resolves to the bogus `javafx.base:11.0.0-SNAPSHOT` (note the dot, not the real `javafx-base` artifact), which is not in any mirror, so the build cannot resolve deps. Fix: override hibernate-validator to >= 6.2.0.Final (it switched to the real `org.openjfx:javafx-base` at `provided` scope). If the module imports a BOM (`spring-boot-starter-parent`) that re-pins the old version, declare the `hibernate-validator` `<version>` first in that module's own `<dependencyManagement>` so it wins over the imported BOM. (Verified: jaxxy-rs/jaxxy `jaxxy-example`, overriding 6.0.11.Final -> 6.2.0.Final removed the phantom `javafx.base` node.)

- **`package org.hamcrest does not exist` / `cannot find symbol: assertThat` / `Tests run: 0` right after a JUnit 4 to 5 change:** a JUnit 4 to 5 migration (including the one carried inside every `UpgradeSpringBoot_*`) removes `junit:junit`, and `hamcrest-core` and the JUnit platform launcher only ever arrived transitively through it. Nothing declares them afterwards, so either the test sources stop compiling or the runner discovers zero tests and every conserved test counts as lost. Fix by declaring what the migration removed: add `org.hamcrest:hamcrest:2.2` (test scope) whenever test sources still import `org.hamcrest`, and add `org.junit.platform:junit-platform-launcher` (test scope) when the runner reports no tests. Both are build-config additions, so they are allowed and they cost one edit.
- **`cannot find symbol` for a generated getter, builder, mapper or query type after the recipe ran:** the recipe injected an `<annotationProcessorPaths>` block into `maven-compiler-plugin`, and that switches off javac's discovery of processors sitting on the plain classpath. Every processor the project relied on implicitly stops running, and the error names the generated symbol rather than the processor, so it reads like an API break. Fix by re-adding each processor still listed in `<dependencies>` as an explicit `<path>` entry (Lombok, `mapstruct-processor`, `querydsl-apt`, `auto-service`, `spring-boot-configuration-processor`). Signature worth knowing: MapStruct reports `Cannot find implementation for ...` at runtime instead of failing the compile.

## General discipline (these stop you chasing non-problems)
- **edit hygiene:** after every build-file edit, before rebuilding, validate it, `./gradlew help -q` (Maven `mvn -q validate`). If that fails naming the file you just edited, your edit broke the script: fix/revert it, do not chase it as a migration error. Make minimal, validated edits.
- **not your tests:** tests needing Docker/Testcontainers/a database/Selenium/the network were already failing in the baseline, not in your conserve-set. Only tests that passed under 8 must still pass.
- OOM during tests is usually downstream of an earlier real error. Fix the first error first.
- Stale generated classes (`ClassAlreadyExistsException`, jsonschema2pojo): `./gradlew clean` / `rm -rf target`, re-run.

## Absolutely forbidden (cheating: fails the run)
Do not delete/skip/disable/weaken any test or assertion; no `-DskipTests`/`<skip>true`; do not delete test files; do not touch test code to make it pass. Conservation of the originally-passing tests is checked independently.

## Target gate: run this before you say done (this is where the reward is won or lost)
The most common miss is stopping at a green build: a wrapper bump, a `chmod +x gradlew`, a dependency floor, or an OpenRewrite run can all produce a clean compile+test while the bytecode still targets 8 -> `FAIL_target_not_bumped edits 0`, which earns nothing. The reward follows the effective bytecode version, not the build succeeding. So before you declare done, every run:
1. Grep the whole tree for any target pin still below 11 (one simple grep; do not improvise a `find -exec`):
   `grep -rnE 'JavaLanguageVersion\.of\(([0-9]|10)\)|VERSION_(1_[0-8]|[89]|10)\b|source[Cc]ompatibility|target[Cc]ompatibility|options\.release|<source>|<target>|<release>|maven\.compiler\.(source|target|release)|jvmTarget' . 2>/dev/null | grep -vE '/build/|/target/'`
   This must return nothing below 11. It is how you catch the module, or the Kotlin `jvmTarget` block, you would otherwise miss. Bump every match, in the root and every module, using the forms listed under "verify the Java target actually landed" above.
2. Confirm a compiled main class is major 55: `f=$(find . -path '*classes/*/main/*.class' -o -path '*/target/classes/*.class' 2>/dev/null | grep -v module-info | head -1); od -An -tx1 -j6 -N2 "$f"` -> the second byte must be `37` (=55). A green build whose main classes are still `34` (=52) is not a bump.

Only when the build is green, no tests are lost, and both checks pass: say you are done and summarize what you changed.

- **Apply the recipe only via `bapply` (transient init-script). never add the OpenRewrite plugin to a build file.** If you add `id('org.openrewrite')` / `apply plugin: 'org.openrewrite.rewrite'` / a `rewrite { }` block / `rewrite(...)` deps to `build.gradle` to apply the recipe. It persists into the gate build, which resolves plugins from the offline mirror and dies: `Plugin Repositories (could not resolve plugin artifact 'org.openrewrite:org.openrewrite.gradle.plugin:N')` -> FAIL_build_post. `bapply` applies the recipe transiently and leaves no residue; if you already added it, remove the plugin + `rewrite{}` block + `rewrite(...)` deps before the gate. (rr_8_93 aartiPl/tablevis: agent left `id('org.openrewrite') version '6.40.0'` -> gate couldn't resolve it offline.)

- **Gradle wrapper jar missing (`./gradlew` -> `Error: Could not find or load main class org.gradle.wrapper.GradleWrapperMain` / `ClassNotFoundException`):** the repo committed `gradlew` but not `gradle/wrapper/gradle-wrapper.jar` (often the `.properties` too) - the wrapper cannot bootstrap. The baseline usually passed via system gradle because `gradlew` was non-executable (the harness `tool()` uses `./gradlew` only if it is executable). So do not `chmod +x gradlew` here - the wrapper-floor `chmod` forces the harness onto the broken wrapper and fails the gate; leave `gradlew` non-executable so it falls back to system gradle, or regenerate the wrapper (`gradle wrapper --gradle-version <pin>` to create the jar). Only `chmod +x gradlew` when `gradle/wrapper/gradle-wrapper.jar` actually exists. (rr_8_102 akosogova/chessRating: empty `gradle/wrapper/`; leaving gradlew non-exec + bumping the toolchain `of(8)`->`of(11)` -> PASS 17/17 target 11.)