Getting off Java 8: what actually breaks
The four failures that stop most Java 8 migrations, and how to clear them.
Open this lesson in the learning hubKey points
- The single biggest blocker is removed internal APIs. Java 9 encapsulated
sun.misc.Unsafeand thejavax.xml.bindfamily moved out of the JDK entirely, so code or libraries using them fail to compile or fail at runtime. - Second is reflection into the JDK. Libraries that call
setAccessibleon JDK internals got warnings in 9-16 and hard errors from 17, which is why an upgrade that worked on 11 can break on 17. - Third is bytecode level. Old versions of ASM, cglib, Lombok and mocking libraries cannot read newer class files, and the error - often an
ArrayIndexOutOfBoundsExceptiondeep inside a library - names nothing useful. - Fourth is date and locale data. Java 9 switched to CLDR as the default locale provider, so formatted dates and numbers change subtly, which silently breaks assertions and report output.
- The tool for finding all of this is
jdeps. Run it against your full dependency set to list every internal API used, before changing a single line. - Go via 17 rather than jumping straight to 21 or 25. Each LTS has its own removals, and debugging one set of failures at a time is much faster than debugging three.
Example
# 1. Find internal API usage BEFORE you start.
$ jdeps --jdk-internals --multi-release 21 -R -cp "libs/*" build/libs/app.jar
# app.jar -> JDK removed internal API
# com.example.Hack -> sun.misc.Unsafe JDK internal API (JDK removed)
# Use --print-module-deps to see what module each dependency needs.
# 2. The javax.* packages that LEFT the JDK. Add them back explicitly:
# javax.xml.bind -> jakarta.xml.bind-api + jaxb-runtime
# javax.activation -> jakarta.activation-api
# javax.annotation -> jakarta.annotation-api
# java.xml.ws -> jakarta.xml.ws-api
# com.sun.image.codec -> use ImageIO
# 3. Reflection into the JDK: a warning on 11, an ERROR from 17.
# Temporarily reopen, THEN fix the library:
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
# Treat these as a migration crutch. Every one you keep is a library
# that will break again on the next upgrade.
# 4. Locale data changed in Java 9 (CLDR). Restore the old behaviour
# temporarily if reports or tests depend on it:
-Djava.locale.providers=COMPAT,CLDR
# COMPAT is deprecated - use it to unblock, then fix the assertions.
---
# THE ORDER THAT WORKS:
# 1. upgrade the BUILD and its plugins first, still targeting 8
# 2. upgrade libraries to versions that support the new JDK
# 3. compile with --release 8 on the new JDK (catches API misuse early)
# 4. move to 11, then 17, then 21 - one LTS at a time
# 5. only then start using new language features
#
# Doing 5 before 4 is the classic mistake: now you cannot roll back.
# Verify what you actually produced:
$ javap -verbose -cp build/classes Main | grep "major version"
# 52 = Java 8, 55 = 11, 61 = 17, 65 = 21, 69 = 25
Run jdeps before changing anything, move one LTS at a time, and treat every --add-opens as a library you still have to fix.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Java 8 Course course, and every lesson in it is listed on the Java 8 Course contents page.