# Upgrade a Spring Boot App to Java 25

Yes. An agent moves your Spring Boot build to Java 25, fixes what the new JDK breaks and opens a pull request with passing tests.

[Run this on Rush](https://cloud.getrush.ai/run/upgrade-java-version/spring-boot/), in a Project that has your repository.

## What the agent does

The agent clones the repository into its own cloud sandbox and finds every place the Java version is written down, because a version upgrade that misses one of them passes locally and fails somewhere else. In a project like spring-projects/spring-petclinic there are two builds side by side. `pom.xml` inherits from `spring-boot-starter-parent` `4.1.0` and sets `<java.version>17</java.version>`, which also feeds the `maven-enforcer-plugin` rule `requireJavaVersion` and the `java.source` and `java.target` values written into `build-info`. `build.gradle` applies the `org.springframework.boot` plugin `4.1.0` and sets a toolchain with `JavaLanguageVersion.of(17)`. The wrappers are Maven `3.9.16` in `.mvn/wrapper/maven-wrapper.properties` and Gradle `9.5.1` in `gradle/wrapper/gradle-wrapper.properties`, and the agent checks that both run on a JDK 25 before touching anything else.

Next it reads the plugins that care about the bytecode version. JaCoCo is pinned at `0.8.15` and instruments every test class; the GraalVM native build tools, `spring-javaformat` `0.0.47` and the CycloneDX plugin all run during the build. Checkstyle is held at `12.3.1`, with a comment in `pom.xml` saying it stays on 12 because Checkstyle 13 needs JDK 21. On Java 25 that reason is gone, so the agent says so in the pull request and leaves the bump as a separate decision rather than mixing it into the JDK change. It moves the version in one place per build: `java.version` to 25 in `pom.xml` and the toolchain to `JavaLanguageVersion.of(25)` in `build.gradle`.

Then it updates what runs the build outside the sandbox. `.github/workflows/maven-build.yml` and `gradle-build.yml` both use a `java: [ '17' ]` matrix with `actions/setup-java@v4` and `distribution: 'adopt'`, which setup-java has removed in favour of `temurin`, so the agent switches the distribution to `temurin` and the matrix to 25. It notes that `gradle-build.yml` caches with `cache: maven` even though it runs `./gradlew build`, and changes it to `gradle`. There is no `Dockerfile` (the README builds images with `./mvnw spring-boot:build-image`), but `.devcontainer/Dockerfile` defaults to `VARIANT=17-bullseye` for Gitpod and `devcontainer.json` asks for Java `21-oracle`, so the agent updates both or explains why one stays.

Finally it builds and tests on JDK 25 with both tools: `./mvnw -B verify`, which runs the enforcer, the javaformat check and the nohttp Checkstyle rules before the tests, and `./gradlew build`. The project has 20 test classes. `MySqlIntegrationTests` is marked `@Testcontainers(disabledWithoutDocker = true)` and `PostgresIntegrationTests` assumes `DockerClientFactory.instance().isDockerAvailable()`, so the agent reports whether those two ran or were skipped in the sandbox; the workflows on the pull request run them on GitHub's runners. When a compile error, a deprecation that is now an error or a failing test appears, it fixes the cause, runs the suite again, and only then opens the pull request.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "Upgrade this Spring Boot project to Java 25. Find every place the Java version is set: java.version in pom.xml, the Gradle toolchain in build.gradle, the maven-enforcer rule, the GitHub Actions workflows (use the temurin distribution, since setup-java removed adopt), the devcontainer and any Dockerfile. Check that the Maven and Gradle wrappers and the build plugins (JaCoCo, GraalVM native build tools, spring-javaformat, Checkstyle) work on JDK 25 and update only the ones that need it. Build and run the tests on JDK 25 in the sandbox with ./mvnw -B verify and ./gradlew build, fix what breaks, and say which tests were skipped and why. Open a pull request listing each file changed, each fix, and anything left for a separate change.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a pull request on a new branch. In a project shaped like spring-petclinic it changes `pom.xml` and `build.gradle` to Java 25, both workflow files to `temurin` with a Java 25 matrix (and the Gradle workflow's cache to `gradle`), and the devcontainer files, plus any source or test fixes the new JDK forced. The description lists each changed file with the reason, every compile or test failure the agent hit with the fix it made, the tests that were skipped in the sandbox and why, and the follow-ups it chose not to mix in, such as moving Checkstyle to 13.

The pull request's own checks are the proof that matters: the Maven and Gradle workflows it edits run on it with JDK 25, including the Testcontainers tests on a runner with Docker. The session's transcript keeps every command the agent ran in its sandbox, from `java -version` to each `./mvnw -B verify` and `./gradlew build`, with the output.

Before merging, a reviewer checks that the JDK distribution matches what the team deploys on, that the base image used in production (built by `spring-boot:build-image` or elsewhere) will also run Java 25, that any `--add-opens` flag or suppressed warning the agent added has a reason in the description, and that both workflows passed on the pull request rather than only one.

## Run it on every new JDK release

Java ships a feature release every March and September. A scheduled job on your server can send this request with the new version in the prompt once your JDK vendor publishes it, one request per repository, each with that repository in `resources`. Each run gets its own sandbox and opens its own pull request, so a team with many Spring Boot services sees which ones move cleanly and which need work.

Set a webhook URL on the API key and Rush POSTs `run.completed` or `run.failed` when each run's turn finishes, with the `session_url`. A key runs at most `max_concurrent` sessions at once (4 by default) and answers `429` past that, so a job covering many repositories should queue its requests.

## Why run it on Rush?

A JDK upgrade touches the machine as much as the code: a new JDK, both wrappers downloading their distributions, every dependency resolved again, and a full test run that has to happen on the new version and not on whatever `JAVA_HOME` points at on a laptop. In a Rush sandbox the agent installs and uses JDK 25 on its own disk, so no one's local toolchain changes and the build is not quietly running on Java 17.

The proof is in two places. The transcript keeps each `./mvnw` and `./gradlew` run with its output, and the pull request's workflows run the same builds on JDK 25 on GitHub. A reviewer can see that the upgrade was built and tested on the new version rather than taken on trust, and several services can be upgraded at once, each in its own sandbox.

## Frequently asked questions

### My project has both Maven and Gradle builds. Does it update both?

Yes. The agent finds the Java version in each build file, moves both, and runs both `./mvnw -B verify` and `./gradlew build` on JDK 25. If one of them fails and the other passes, the pull request says which and why.

### Will it upgrade Spring Boot at the same time?

No, unless you ask. The prompt changes the Java version and only the plugins that need it to run on JDK 25. A Spring Boot upgrade is a separate change with its own risks, and keeping them apart makes each pull request easier to review and revert.

### What about integration tests that need Docker?

In spring-petclinic the MySQL and Postgres tests skip themselves when Docker is not available. The agent reports in the pull request whether they ran or were skipped in its sandbox, and the workflows on the pull request run them on GitHub's runners, which have Docker.

### Why switch the setup-java distribution?

The workflows use `distribution: 'adopt'`, a legacy AdoptOpenJDK distribution setup-java no longer supports, so the setup step fails before the build starts; `temurin` replaces it. The agent switches to `temurin`, the Eclipse Adoptium builds; if your team uses another distribution, name it in the prompt.
