Java 6 to Java 21: the migration nobody budgets for
COBOL gets most of the attention in modernization conversations, but many of the systems we hear about are just as constrained by Java code written for Java 5, 6 or 7. These applications run, often on application servers that are themselves out of support, and upgrading them is consistently underestimated. This note lists what tends to break on the way to Java 21.
Removed modules
Java 11 removed the Java EE and CORBA modules from the JDK (JEP 320). Code that imports javax.xml.bind (JAXB), javax.xml.ws (JAX-WS) or javax.activation no longer compiles without adding those libraries explicitly. Newer versions of those libraries live under the jakarta namespace, which turns a dependency change into a source change across every file that uses them.
Strong encapsulation
Older libraries often reach into JDK internals through reflection or sun.* classes. From JDK 16 the internals are strongly encapsulated by default (JEP 396), and JDK 17 removed the --illegal-access escape hatch (JEP 403). Old versions of serialization, mocking and bytecode libraries are frequent offenders. The fix is usually a library upgrade, which can cascade.
Removed and deprecated APIs
- The Nashorn JavaScript engine was removed in Java 15.
- The Security Manager was deprecated for removal in Java 17 (JEP 411).
- Finalization was deprecated for removal in Java 18 (JEP 421).
- Constructors such as
new Integer(int)are deprecated for removal. Thread.stopthrowsUnsupportedOperationExceptionfrom Java 20.
Behaviour changes
Not everything fails at compile time. Default character sets, date and time formatting data and TLS defaults have all changed across releases. Java 18 made UTF-8 the default charset (JEP 400), which matters for any code that reads files without specifying an encoding. These are exactly the differences golden-master tests catch and code review misses.
The opportunity
An upgrade is also a chance to make the code easier to maintain. Java 21 offers records for data carriers, sealed types, pattern matching in switch, and virtual threads (JEP 444) for blocking I/O code. The temptation is to adopt all of these at once. We prefer two passes: first a faithful upgrade that keeps behaviour identical, then targeted refactors, each in its own pull request.
Why this suits agents
A Java upgrade is a long series of small, mechanical, verifiable fixes: change an import, upgrade a library, adjust a call, rebuild, run the tests, repeat. It is tedious for people and well suited to an agent working inside a test harness. The judgement calls, such as which library replaces an abandoned one, are surfaced for a person to decide.
Older Java (6 to 8) to Java 21 is one of the two migration paths we are focusing on first, alongside COBOL.