What we are building
The deliverable is a real upgrade: take a legacy Java 8 microservice, run an agent over it, and end with a new git branch where the app builds and tests green on Java 21. Not a toy. The agent rewrites javax.* to jakarta.*, bumps Spring Boot across a major version, migrates JUnit 4 to JUnit 5, replaces deprecated APIs, and updates your build file, then proves the result by compiling and testing on your machine.
The tool is AWS Transform custom and its CLI, atx. This is the part worth saying out loud, because the obvious answer is now wrong. If you searched this six months ago you would have found Amazon Q Developer's /transform and the qct CLI. AWS announced on April 30, 2026 that Amazon Q Developer IDE plugins and paid subscriptions reach end of support on April 30, 2027, and that new signups stopped on May 15, 2026. The Java upgrade capability moved to AWS Transform, which is generally available, billed per agent minute, and open to any AWS account. So this tutorial uses the path that will still exist next year.
The non-obvious design choice that makes AWS Transform good for an engineer who does not trust agents: the planning and reasoning run server-side, but the build and the tests run on your machine, and every step is committed to a fresh git branch. The diff review is not a feature bolted on top. It is the native output. You end with git log showing the agent's work as discrete commits you can read, revert, or cherry-pick. And you pay for the agent's thinking, not for your local mvn builds.
Prerequisites
Be honest with yourself about this list before you start, because the agent will refuse to run if any piece is missing.
You need an AWS account with credentials that can call AWS Transform custom. The simplest grant is the AWS managed policy AWSTransformCustomFullAccess attached to your IAM user or role. AWS Transform custom runs in a specific set of regions; pick us-east-1 (N. Virginia) or eu-central-1 (Frankfurt), both confirmed supported. You need Node.js 22 or later, because the CLI ships on Node. You need Git, and your project directory has to be a valid git repo. You need two JDKs installed locally: the source JDK (Java 8) and the target JDK (Java 21), because the agent builds with both. You need Maven for this sample, at a version the agent accepts.
One platform gotcha: AWS Transform custom supports Linux, macOS, and Windows Subsystem for Linux. Native Windows is not supported and the CLI will detect it and exit. If you are on Windows, do everything below inside WSL.
Assumed knowledge: you can read a pom.xml, you know what JAVA_HOME does, and you have upgraded a Java app by hand at least once so you know what the agent is saving you from.
Setup
Install the CLI with the official script, then confirm it is on your path.
curl -fsSL https://transform-cli.awsstatic.com/install.sh | bash
atx --version
Point the CLI at a supported region and give it credentials. Environment variables are the least surprising way to do this for a one-off run.
export AWS_REGION=us-east-1
export AWS_ACCESS_KEY_ID=your_access_key
export AWS_SECRET_ACCESS_KEY=your_secret_key
# if you use temporary credentials:
export AWS_SESSION_TOKEN=your_session_token
Now grab the sample. AWS maintains a deliberately dated Spring Boot service for exactly this exercise: aws-appconfig-java-sample, a Java 1.8 microservice that lists free movies for the month, pinned to Spring Boot 2.x, Log4j 2.13.x, Mockito 1.x, javax, and JUnit 4. It is legacy on purpose.
git clone https://github.com/aws-samples/aws-appconfig-java-sample.git
cd aws-appconfig-java-sample
export JAVA_HOME=/path/to/your/jdk-8
mvn clean install
That last build is the smoke test, and it matters more than it looks. The agent validates by building the project before and after, so a baseline that does not compile on Java 8 will sink the whole run. Get a green build on Java 8 first. Then commit a clean baseline so the agent has a stable starting point and you have something to diff against.
git add .
git commit -m "Baseline before Java 21 transformation"
git status # must report a clean working tree
Step 1: See which transformation you actually want
Before configuring anything, list the transformations the service offers. This is not busywork. The catalog changes, and the exact name you pass has to match.
atx custom def list
You will see AWS-managed transformations alongside any your organization has defined. The one we want is AWS/java-version-upgrade, described as upgrading Java applications on any build system from any source JDK to any target JDK, including Jakarta EE migration, database drivers, ORM frameworks, and Spring ecosystem updates. Nearby you will spot adjacent tools worth knowing about: AWS/java-aws-sdk-v1-to-v2 for SDK migrations, AWS/early-access-log4j-to-slf4j-migration, and AWS/oracle-java-to-corretto. Note that a one-shot Java version upgrade is the supported managed path. There is no managed "Spark upgrade"; if your legacy pain is Spark specifically, you would drive that through a custom-defined transformation, which is a later episode.
The mental model: AWS-managed transformations are pre-built and AWS-vetted, ready with no setup. You are renting a tested playbook rather than prompting a blank agent and hoping.
Step 2: Write the config that steers the agent
You can run a transformation in one line, but the interesting control lives in a config file. Create config.json next to the project.
{
"codeRepositoryPath": "/absolute/path/to/aws-appconfig-java-sample",
"transformationName": "AWS/java-version-upgrade",
"buildCommand": "mvn clean install",
"validationCommands": "build and validate using \"mvn clean install\" after transformation to test with Java 21",
"additionalPlanContext": "This is a Java 8 to 21 upgrade of a Maven app. Include all dependency migration. Use java path /path/to/jdk-8/bin/java and /path/to/jdk-21/bin/java when building before and after transformation. Migrate javax to jakarta, JUnit 4 to JUnit 5, and update Spring Boot to a Java 21 compatible version. Check for deprecated methods and dependencies and update them."
}
Three fields carry the weight. transformationName selects the managed playbook. buildCommand is what the agent runs to validate, so it must be the command that actually compiles your project. additionalPlanContext is the steering wheel: free text that the agent folds into its plan. This is where you name the target Java version, point at both JDK paths, and call out the migrations you expect, like javax to jakarta. The agent can infer the target from interactive chat, but writing it down removes ambiguity and makes the run repeatable across a fleet of repos.
Use an absolute path for codeRepositoryPath. Relative paths bite you when the CLI resolves its working directory differently than you expect.
Step 3: Run it interactively and approve the plan
Run the transformation with the config, in interactive mode so you can read the plan before any code changes.
atx custom def exec -t --configuration file://config.json
The agent analyzes the project and prints a transformation plan: the Java version change, the API migrations, the Spring Boot bump, the dependency updates, the build-system changes, and the code-pattern rewrites it intends to make. Read it. This is the cheapest moment to catch a wrong assumption. If something is missing, for example a legacy dependency you know is buried in a submodule, type your correction as feedback and the agent revises the plan before touching anything.
When the plan looks right, type proceed and press Enter. The agent now works in steps. It rewrites code server-side, ships each step to your machine to build and test, and on success commits that step to a new branch before moving on. That round trip is the whole point. The agent never asks you to trust an unbuilt blob; it builds on your hardware, with your private dependencies reachable, and only advances when the local build is green.
Cost shows up here, and it is smaller than people fear. AWS Transform custom bills $0.035 per agent minute, and only for active server-side reasoning, not for the local builds, which are the slow part. AWS's own published example puts a roughly 17,000-line Java version upgrade at about 72 agent minutes, or $2.52. This sample is a few hundred lines, so expect a bill in cents. If you want a hard ceiling, set an AWS Budget on agent minutes before you run, and remember you can pause a transformation in the CLI at any time and resume within 24 hours.
Step 4: Review the diff like a pull request
The transformation finished on a new branch. Treat it exactly like a teammate's PR.
git branch # find the new transformation branch
git log --oneline # read the agent's commits, step by step
git diff main # full diff against your baseline
Because each phase is its own commit, the history is legible instead of one 4,000-line dump. You will see the pom.xml move Java 8 to 21 and Spring Boot across its major boundary. You will see import javax.persistence.* become import jakarta.persistence.*. You will see JUnit 4 @Test and Assert.assertEquals migrate to JUnit 5 org.junit.jupiter equivalents. You will see raw collection types gain generics and deprecated security APIs swapped for CertificateFactory and friends. Read each commit, and where you disagree, this is normal git from here: revert a commit, amend it, or open the PR and request changes from your human reviewers.
The service also writes a report describing what it did and, if any validation did not pass, what it recommends you finish by hand. Read that report before you merge. An agent that says "I could not get the integration test green, here is why" is more useful than one that silently leaves a broken build.
Verify it works
The contract for this tutorial: after the run, the app builds and tests on Java 21. Switch your JDK and build the transformed branch.
export JAVA_HOME=/path/to/your/jdk-21
mvn clean install
You are looking for BUILD SUCCESS with the tests executing under JUnit 5, not skipped. Confirm the version actually moved by checking the build file rather than trusting the log:
grep -A1 "maven.compiler" pom.xml # or <java.version> / <release>
# expect 21, not 1.8
If the build is green on 21 and the pom.xml reports 21, the upgrade is real. Run the service and hit its endpoint if you want the belt-and-suspenders check, but a green test suite on the new runtime is the signal that counts.
When it breaks
The baseline build fails on Java 8. This is the most common stumble and it happens before the agent does anything interesting. Your JAVA_HOME is probably pointing at the wrong JDK. Set it explicitly to your Java 8 install and confirm mvn clean install is green before you run atx.
The CLI exits immediately on Windows. AWS Transform custom does not support native Windows and detects it on launch. Move into WSL and run everything from there. There is no flag that overrides this.
You get a region error. AWS Transform custom only runs in a fixed set of regions. If AWS_REGION is unset or points somewhere unsupported, the CLI tells you and stops. Set export AWS_REGION=us-east-1 (or another supported region like eu-central-1) and rerun.
The agent finishes but the code does not build. It happens, and agent minutes still accrue regardless of the end state, so you are not getting a refund for a miss. The agent leaves you the latest in-progress files and usually a recommendation for finishing the job. Read the report, give targeted feedback, and rerun the transformation rather than starting from scratch.
The run refuses because of a dirty tree. The agent wants a clean git state so its commits are meaningful. If git status shows uncommitted changes, commit or stash them first. This is the same discipline you would apply before a rebase.
Where to take it next
Three extensions, easiest to hardest.
Automate it. Swap interactive mode for direct mode and the agent runs without prompts, which is what you want inside CI. The non-interactive form is atx custom def exec -n AWS/java-version-upgrade -p ./your-project -c "mvn clean install" -x -t, where -x executes automatically and -t enables validation. Drop that into a pipeline step and you have unattended upgrades.
Run it at fleet scale. The same job state is shared across the CLI and the optional AWS Transform web console. Start a job in your terminal, then open the console (it needs IAM Identity Center) to watch agent minutes, time saved, and progress across many repositories at once. This is how you turn a one-app demo into a portfolio campaign.
Move past version upgrades into your own rules. When no managed transformation fits, for example a proprietary framework migration or that Spark upgrade, you define a custom transformation conversationally in atx, test it on a sample, and publish it to your org's registry. AWS also ships the same agents as an AWS Transform Power in Kiro and a VS Code extension, so you can start a job in the IDE and finish it in the console against one underlying job.
Here is the reframing worth carrying out of this episode. The interesting question stopped being "can an agent upgrade my Java." It can. The question is whether you can review what it did fast enough to trust it at the scale of a real codebase. AWS Transform answers that by making git the interface: every step a commit, every commit a thing you can read. The agent that shows its work in diffs is the only kind you should let near production.

