---
title: "How to upgrade a critical Java 8 application to modern Java"
description: "A practical plan to move a critical Java 8 application to a current LTS release: dependency inventory, stepped upgrades, tests, pitfalls and load testing."
canonical: https://sdk.enterprises/en/insights/upgrading-legacy-java
language: en
---

# How to upgrade a critical Java 8 application to modern Java

Updated: 2026-09-25

> Upgrade a critical Java 8 application in stages: inventory every dependency, move to Java 11 first, then to each later long-term support release, and let tests and load tests decide when each step is safe. Most of the work sits in libraries, build tooling and code that touches JDK internals, not in your business logic.

## Staying on Java 8 narrows your options every year

A Java 8 application can keep running for years, and that is the trap. Security fixes increasingly depend on a paid support contract or a distribution that still backports them, and major libraries have moved on. Spring Boot 3, for example, requires Java 17 as a minimum, so staying on Java 8 also freezes your framework versions.

Newer JVMs also run the same code better. G1 has been the default garbage collector since Java 9, low-pause collectors such as ZGC are production-ready, and compact strings reduce memory use for text-heavy workloads. Engineers also expect modern language features such as records, text blocks and switch expressions, which makes a Java 8 codebase harder to staff.

## Start with an inventory of everything the application depends on

The JDK upgrade itself is rarely the hard part. The hard part is the long tail of libraries, plugins and agents that were written for Java 8 and never updated. Build a complete list before changing anything, and record the version of each item.

- The JDK vendor and version in every environment, from developer laptops to production.
- Direct and transitive dependencies, exported from the Maven dependency tree or the Gradle dependencies report.
- Build plugins, code generators and annotation processors such as Lombok.
- The application server or servlet container, if the application runs inside one.
- Java agents for monitoring, profiling or security, which hook deeply into the JVM.
- Code that uses internal JDK APIs, found with the jdeps tool and its jdk-internals option.
- JVM flags, garbage collector settings and any scripts that parse the java -version output.

## Step through long-term support releases instead of jumping

Move from Java 8 to 11, then to 17, then to 21 or 25. Each hop has its own set of removals and behavior changes, and a single jump mixes them all into one failure you cannot diagnose. Fixing one layer at a time keeps every step small enough to review and roll back.

Where you can, upgrade libraries while still on Java 8, since many recent versions support both old and new JDKs. Then run the existing build on the new JVM before you change the compilation target. This separates runtime problems from compiler problems, and the release compiler flag keeps the bytecode target explicit.

## Make the test suite the gate for every step

Tests are your only objective signal that the upgraded application still behaves the same. If critical paths have little coverage, write characterization tests first: tests that capture what the system does today, including its odd edge cases, without judging whether that behavior is right.

Add integration tests that run against a real database and real message brokers, because many upgrade failures only appear at those boundaries. For calculation-heavy systems, replay the same inputs through the old and new versions and compare outputs field by field. Pay attention to dates, numbers and text: Java 9 switched to CLDR locale data by default and Java 18 made UTF-8 the default charset, and both can change formatting or parsing silently.

## Know the pitfalls that break Java 8 code

Most breakages fall into a few known categories. Search for each one before the upgrade instead of discovering it in production.

- Removed Java EE and CORBA modules: Java 11 dropped JAXB, JAX-WS, JavaBeans Activation and the common annotations from the JDK, so they must be added back as explicit dependencies.
- Strong encapsulation of JDK internals: illegal reflective access produced warnings from Java 9, is denied by default since Java 16 and cannot be switched back on globally since Java 17. Fix it by upgrading the library; use add-opens only as a documented, temporary exception.
- Outdated bytecode tools: older versions of Lombok, Mockito, Byte Buddy, ASM and build plugins fail on newer class file versions.
- Removed features: the Nashorn JavaScript engine was removed in Java 15, and the Security Manager was deprecated in Java 17 and permanently disabled in Java 24.
- Removed JVM flags: the CMS garbage collector was removed in Java 14, and a JVM started with removed options can refuse to boot.

## Prove performance under realistic load, not on a laptop

A new JDK changes the garbage collector, the JIT compiler and memory layout, so performance can move in either direction. Run a load test that reproduces the production traffic profile, on the same hardware or instance type, before and after each step. Compare latency percentiles, throughput, memory and garbage collection pauses, and let the JIT warm up before you measure.

Our engineers led a Java 8 to 16 migration of a critical gas day-trading application for a national gas trading desk, working through Sopra Steria. On that system, capacity and energy-flow calculations had to stay correct and fast under high-frequency load, so the upgrade went together with optimization work on those calculations and was validated under load rather than assumed.

## Roll out gradually and keep a way back

Ship the upgraded runtime to one instance or a small share of traffic first, and watch error rates, latency and garbage collection behavior against the Java 8 baseline. Keep the previous build deployable until the new version has run through normal business cycles, including month-end or peak periods.

Only then remove the Java 8 artifacts and start using new language features. If you want help planning or running an upgrade like this, SDK Enterprises staffs it with in-house engineers and vetted specialists under one accountable contract.

## Key takeaways

- The effort in a Java 8 upgrade sits mostly in dependencies, build tooling and use of JDK internals, not in business logic.
- Step through long-term support releases one at a time so each failure has a single, findable cause.
- Characterization and integration tests are the gate for every step, especially around dates, numbers and text encoding.
- Validate performance under a production-like load, because garbage collection and JIT changes can move results either way.
- Roll out to a small share of traffic first and keep the Java 8 build deployable until the new runtime has proven itself.

## FAQ

### Can I upgrade directly from Java 8 to Java 21?

You can, but you inherit every change from Java 9 to 21 at once, which makes failures hard to trace. Stepping through 11 and 17 costs a little more build time and saves a lot of debugging on a critical system.

### How long does a Java 8 upgrade take?

It depends on the number of dependencies, how many use JDK internals, test coverage and how much load testing the system needs. An inventory and a trial run on the new JVM give you a realistic estimate early, before you commit to a timeline.

### Do I need to rewrite code to use new Java features?

No. Java keeps strong backward compatibility for code that sticks to standard, non-deprecated APIs. Adopt records, text blocks and other features gradually, after the upgraded runtime is stable in production.

## Related services

- [Custom software](https://sdk.enterprises/en/services/product-engineering)
- [Application security](https://sdk.enterprises/en/services/secure-systems)
