Definition
- Incremental hops: migrate 8 to 11 first (fix removed modules), then 11 to 17 (fix strong encapsulation). Test thoroughly at each hop.
- Removed modules: add Jakarta dependencies for JAXB, JAX-WS, Common Annotations (same as Java 11 migration).
- Strong encapsulation: Java 17 removes
--illegal-accessentirely. Reflective access to JDK internals fails withInaccessibleObjectException. Fix with--add-opensflags or library upgrades. - Library updates: bytecode-manipulating libraries (Hibernate, Lombok, Mockito) need Java 17-compatible versions.
- New features: records, sealed classes, pattern matching for
instanceof, switch expressions, text blocks all reduce boilerplate. - Use
jdeps --jdk-internalsto detect problematic API usage before migration.
Migrating from Java 8 to 17 at National Bank of India is like moving from a 10-year-old building to a modern tower. Most furniture (code) fits, but some old electrical outlets do not work (removed APIs), some doors need new keys (strong encapsulation), the cafeteria moved (Java EE to Jakarta), and some old equipment is incompatible (library updates needed). Plan: test everything, upgrade one floor at a time.
Understanding the Concepts
What is the recommended migration approach?
Migrate in hops: Java 8 to 11, then 11 to 17. Each LTS boundary introduces specific breaking changes that are easier to fix incrementally.
Java 8 ──→ Java 11 (LTS) ──→ Java 17 (LTS) │ │ │ │ ├── Fix removed ├── Fix strong │ │ Java EE modules │ encapsulation │ │ │ │ ├── Update ├── Update │ │ libraries │ frameworks │ │ │ └──────────────┴────────────────────┴── Run tests
What breaks at each hop?
| Change | Java 8 to 11 | Java 11 to 17 |
|--------|-------------|---------------|
| Removed modules | JAXB, JAX-WS, CORBA | Same (already fixed) |
| Strong encapsulation | Warning only | Error (--illegal-access removed) |
| Default GC | Parallel to G1 | G1 (unchanged) |
| Removed JVM flags | Some CMS flags | CMS removed entirely |
| Framework versions | Spring 4/5, Hibernate 5 | Spring 6, Hibernate 6 |
How do you detect issues before migration?
# Detect use of removed/internal APIs
jdeps --jdk-internals banking-service.jar
# Sample output:
banking-service.jar -> java.base
com.bank.util.XmlHelper -> sun.misc.BASE64Encoder
JDK internal API (java.base)
How do you fix strong encapsulation errors?
# Add --add-opens flags for specific modules
java --add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/java.lang.reflect=ALL-UNNAMED \
-jar banking-service.jar
Better: upgrade the library that uses internal APIs to a version that does not.
What new features can you adopt?
// Records (Java 16+): replace verbose DTOs
record Account(String id, String holder, BigDecimal balance) {}
// Text blocks (Java 15+): multi-line strings
String sql = """
SELECT id, holder, balance
FROM accounts
WHERE branch = 'MUMBAI'
""";
// Pattern matching instanceof (Java 16+)
if (obj instanceof Account acc) {
System.out.println(acc.holder());
}
// Switch expressions (Java 14+)
String type = switch (txn.code()) {
case "N" -> "NEFT";
case "U" -> "UPI";
case "R" -> "RTGS";
default -> "UNKNOWN";
};
What are common mistakes?
WRONG: Jumping directly from Java 8 to 17 without intermediate testing.
WRONG: Adding --add-opens flags without investigating if the library has a newer version that does not need them.
WRONG: Not updating test frameworks (JUnit 4 to JUnit 5, Mockito to latest) alongside the JDK.
What should a senior engineer know?
- The riskiest breakages are not language changes but transitive library dependencies doing deep reflection. Audit them with
jdepsfirst. - Spring Boot 2.x runs on Java 17, but Spring Boot 3.x requires Java 17+ and uses jakarta.* namespace.
- Docker base images need updating:
eclipse-temurin:17-jreinstead ofopenjdk:8-jre. - Consider running the full test suite at each hop. Treat library upgrades, not language changes, as the main source of risk.
Visual Overview
Migration Path:
Java 8
│
├── 1. Compile with Java 11
├── 2. Fix ClassNotFoundException (JAXB, JAX-WS)
├── 3. Add Jakarta dependencies
├── 4. Run tests → all green
│
▼
Java 11
│
├── 5. Compile with Java 17
├── 6. Fix InaccessibleObjectException
├── 7. Update libraries (Spring, Hibernate)
├── 8. Run tests → all green
│
▼
Java 17
│
├── 9. Adopt records, text blocks
├── 10. Adopt pattern matching, switch
└── 11. Final testing
Key Takeaway
Migrating from Java 8 to 17 involves five main steps: compile with Java 17 and fix errors, add explicit Jakarta dependencies for removed Java EE modules, update all libraries and frameworks to Java 17-compatible versions, handle strong encapsulation with --add-opens flags for reflection-heavy code, and run the full test suite.
The three biggest challenges are: JAXB/JAX-WS removal (add Jakarta dependencies), strong encapsulation breaking internal JDK API access (--add-opens or migrate to public APIs), and library compatibility (some older libraries do not support Java 17).
Migrate in hops (8 to 11 to 17) rather than one jump. At National Bank of India, the settlement service migrates first to Java 11 (fixing JAXB), then to Java 17 (fixing reflective access), and finally refactors DTOs to records.
Remember one sentence: migrate in LTS hops, fix removed modules and strong encapsulation at each stop, and treat library upgrades as the main risk.
---