Java 11 Removed/Deprecated Features & Migration

July 21, 20264 min readUpdated 8/20/2026

Java 11 is the upgrade that breaks things. Not because the language changed — it barely did — but because Java 11 removed several modules that had been part of the JDK since the 1990s. Most upgrades from 8 fail for the same handful of reasons, and they are all fixable.

Why this one hurts

Java 9 introduced the module system and split the monolithic JDK into named modules. Anything tagged as "Java EE or CORBA" was deprecated for removal, and Java 11 removed it. Code that had compiled unchanged for fifteen years suddenly could not find classes it had always found.

The upgrade is worth doing anyway: Java 8 has been out of free public support for years, and almost every library you depend on has moved on.

The removed Java EE modules

This is the big one. Six packages left the JDK:

GoneSymptomFix
java.xml.bind (JAXB)NoClassDefFoundError: javax/xml/bind/JAXBContextadd jakarta.xml.bind-api + jaxb-runtime
java.activationjavax/activation/DataSourceadd jakarta.activation
java.xml.ws (JAX-WS)javax/xml/ws/*add jakarta.xml.ws-api
java.xml.ws.annotationjavax/annotation/PostConstructadd jakarta.annotation-api
java.transactionjavax/transaction/*add jakarta.transaction-api
java.corbaCORBA classesno replacement — rewrite

The pattern is the same each time: what was in the JDK is now an ordinary dependency you declare. The classes are unchanged, so adding the artifact is usually the whole fix.

<!-- The two that come up most often -->
<dependency>
    <groupId>jakarta.xml.bind</groupId>
    <artifactId>jakarta.xml.bind-api</artifactId>
    <version>4.0.2</version>
</dependency>
<dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>3.0.0</version>
</dependency>

One trap: the javax.* to jakarta.* rename. Older versions of these artifacts still use javax.* package names and are drop-in; the modern jakarta.* ones require changing your imports. Pick one and be consistent — mixing them produces a classpath where two identically-named classes exist in different packages.

JavaFX and Nashorn

JavaFX left the JDK in Java 11 and became a separate project, OpenJFX. Desktop applications need it added as a dependency and usually as a plugin too, since it ships platform-specific native libraries.

Nashorn, the JavaScript engine, was deprecated in 11 and removed in 15. Code calling new ScriptEngineManager().getEngineByName("nashorn") gets null back rather than an error, so it fails as a NullPointerException somewhere later. The replacement is GraalJS, or reconsidering why the application evaluates JavaScript at all.

Warnings that become errors

# Reflective access into JDK internals: a warning in 9-16, an error from 17
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.example.Something to field java.lang.String.value

Do not ignore these. On Java 11 they are warnings and everything still works; on Java 17 the same call throws InaccessibleObjectException. The libraries that caused this — older Hibernate, Spring, Mockito, Lombok — all have versions that do not, so the fix is almost always a dependency upgrade rather than a code change.

--add-opens will silence it as a stopgap, and treating that as the solution is how a Java 17 upgrade becomes painful two years later.

A migration order that works

  1. Upgrade your build tool and plugins first, on Java 8. Old Maven compiler, Surefire and Shade plugins do not understand Java 11 and produce confusing failures. Get them current before changing the JDK.
  2. Upgrade dependencies next, still on Java 8. Most Java 11 problems are a library's problem, already fixed upstream. Doing this separately means a failure has one possible cause.
  3. Then switch the JDK and set the release level:
<properties>
    <maven.compiler.release>11</maven.compiler.release>
</properties>

Use release rather than source and target. It additionally checks that you only call APIs that exist in that version, which source/target does not — with those, code compiles against the newer JDK's API and then fails at runtime on the older one.

  1. Run jdeps to find removed-module usage before you hit it at runtime:
jdeps --jdk-internals --multi-release 11 target/myapp.jar

It reports every dependency on an internal or removed API, which is a far better list than discovering them one NoClassDefFoundError at a time.

Smaller things that also changed

A short list of deprecations and behaviour changes that produce confusing symptoms rather than clean errors:

  • Thread.stop, Thread.suspend and Thread.resume were already deprecated and now throw. Anything relying on them needs a real cancellation mechanism — an interrupt flag, or a volatile boolean the loop checks.
  • The default garbage collector changed to G1 in Java 9. If you had tuned flags for Parallel or CMS, those flags may now be unrecognised, and an unrecognised GC flag stops the JVM from starting at all.
  • Applets and Web Start are gone. There is no migration path; those applications need rewriting as something else.
  • The version string format changed. 1.8.0_292 became 11.0.2, so any code parsing System.getProperty("java.version") with an assumption about a leading 1. will misread it. Use Runtime.version() instead:
class Demo {
    void run() {
        Runtime.Version version = Runtime.version();
        System.out.println(version.feature());     // 21 — the major version, as an int
        System.out.println(version);               // the full version string
    }
}

What you get

Worth remembering while fixing the fifth NoClassDefFoundError: the new String methods, var, the HttpClient, a dozen API conveniences, single-file execution, and significantly better garbage collectors — G1 became the default in 9, and container awareness landed so the JVM stops ignoring the memory limit on its Docker container.

That last one alone justifies the upgrade for anything running in Kubernetes. A Java 8 JVM in a container sizes its heap from the host's total memory, not the cgroup limit, which is why so many Java 8 containers were killed for exceeding it.

Next

Records is next, opening the Java 17 section — where the language starts changing again in earnest.