This project shows the errors encountered during a Java upgrade and the necessary fixes.
Per Java version there is a Maven module to show what went wrong starting in that version and how it can be fixed.
This project uses Maven, however the issues will be the same with other buildtools.
This readme first describes the issues and solutions for Java in general and then for each specific Java version. After that the various ways to run multiple JDK's on one machine are described.
The last part also describes how to run the examples. For instance the Java 15 examples work on Java 14. When run on Java 15 the broken examples will fail, while the fixed ones will succeed.
OpenRewrite offers various recipes to upgrade your application automatically and even improve your code. It will make the changes locally and then you can decide if you want to commit all of them, or if you want to make changes. Using OpenRewrite can save a lot of time, compared to upgrading manually.
Some of the recipes they provide:
This document describes the bigger changes of Java. There are many more (smaller) items of Java removed. This chapter lists the various categories which were removed. For detailed information, please look at the release notes:
Java release notes
Security roadmap
OpenJDK features
For instance
-XX:+AggressiveOpts
and
-Xoptimize
"The T-Systems Deutsche Telekom Root CA 2 certificate has expired and was removed from the cacerts keystore"
Algorithms deemed unsafe are removed.
For instance the Concurrent Mark and Sweep (CMS) garbage collector was removed in 14.
Parts of the API such as methods can be deprecated and later removed.
It's possible to see the deprecated and removed parts of the API per Java version. For instance via the The Java Version Almanac or via foojay
Some are no longer available, others such as JDK Mission Control and JavaFX now are available as separate builds from various vendors.
Some JDK's such as ojdkbuild and some builds of Liberica JDK still offer a JDK which includes some of the tools.
The EE packages were removed in Java 11. If you still need them, you can add them as Maven/Gradle dependencies.
Please be aware that you should use the new jakarta packages as the old packages are no longer updated.
For instance JAXB can be added with the dependency listed below. However, it's no longer updated, the latest version is 2.3.0.
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
You should instead use the Jakarta dependency which has the newer 3.0.0 version.
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>3.0.0</version>
</dependency>
Example code can be found under java11/javaee_removed_broken
package javax.activation does not exist
cannot find symbol
[ERROR] symbol: class URLDataSource
Example code can be found under java11/javaee_removed_fixed_new_package
Add the necessary dependencies:
<dependency>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
<version>2.0.1</version>
</dependency>
Example code can be found under java11/javaee_removed_broken
package javax.annotation does not exist
cannot find symbol
[ERROR] symbol: class PostConstruct
Example code can be found under java11/javaee_removed_fixed_new_package
Add the necessary dependencies:
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.0.0</version>
</dependency>
Example code can be found under java11/javaee_removed_broken
package javax.transaction does not exist
cannot find symbol
[ERROR] symbol: class TransactionRequiredException
Example code can be found under java11/javaee_removed_fixed_new_package
Add the necessary dependencies:
<dependency>
<groupId>jakarta.transaction</groupId>
<artifactId>jakarta.transaction-api</artifactId>
<version>2.0.0</version>
</dependency>
Example code can be found under java11/javaee_removed_broken
package javax.xml.bind.annotation does not exist
cannot find symbol
[ERROR] symbol: class XmlRootElement
cannot find symbol
[ERROR] symbol: class JAXBException
Example code can be found under java11/javaee_removed_fixed_new_package
Add the necessary dependencies:
For the API there's:
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>3.0.0</version>
</dependency>
For the implementation there are a few options listed below. If you already use one of them as a transitive dependency then it's probably best to use that one to avoid conflicts.
The following command can be used to check if you already use one of the implementations via a transitive dependency:
mvn dependency:tree -Dincludes=org.glassfish.jaxb:jaxb-runtime
mvn dependency:tree -Dincludes=com.sun.xml.bind:jaxb-impl
If you don't have a JAXB implementation as a transitive dependency then it's probably best to use the following Glassfish implementation.
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>3.0.0</version>
<scope>runtime</scope>
</dependency>
Or the jaxb-impl which is now called the Old JAXB Runtime
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>3.0.0</version>
</dependency>
Example code can be found under java11/javaee_removed_broken
package javax.xml.ws does not exist
cannot find symbol
[ERROR] symbol: class Service
Example code can be found under java11/javaee_removed_fixed_new_package
Add the necessary dependencies:
<dependency>
<groupId>jakarta.xml.ws</groupId>
<artifactId>jakarta.xml.ws-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>3.0.0</version>
</dependency>
There's no official replacement/dependency released for Corba
Migrate away from Corba or use something like glassfish-corba
The JDK contained some fonts, but they were removed in Java 11. If the application used these fonts and the operating system doesn't provide them, then an error occurs.
More info from Azul and AdoptOpenJDK
Example code can be found under java11/removed_fonts
java.lang.UnsatisfiedLinkError: /usr/local/openjdk-11/lib/libfontmanager.so:
libfreetype.so.6: cannot open shared object file: No such file or directory
java.lang.NoClassDefFoundError: Could not initialize class sun.awt.X11FontManager
Example code can be found in DockerfileWithFonts
Install the necessary fonts, for instance with:
apt install fontconfig
Depending on your scenario you might need to add some extra packages such as: libfreetype6 fontconfig fonts-dejavu.
Some JDK's automatically install the needed packages.
JavaFX is removed from the JDK and continued as OpenJFX.
Some vendors offer builds of OpenJFX such as Gluon
Some vendors offer JDK builds which include OpenJFX such as Liberica's full version and ojdkbuild
Use the (Maven) dependencies of OpenJFX
Use one of the builds of JDK Mission control:
Nashorn is no longer included in the standard JDK.
Example code can be found under java15/nashorn_broken
java.lang.NullPointerException: Cannot invoke "javax.script.ScriptEngine.eval(String)" because "engine" is null
Example code can be found under java15/nashorn_fixed
Add the necessary dependencies:
<dependency>
<groupId>org.openjdk.nashorn</groupId>
<artifactId>nashorn-core</artifactId>
<version>15.4</version>
</dependency>
Internals of the JDK can no longer be used by default. This mainly impacts tooling which uses low level features of the JDK.
Example code can be found under java16/lombok_broken
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project broken: Fatal error compiling: java.lang.IllegalAccessError: class lombok.javac.apt.LombokProcessor (in unnamed module @0x21bd20ee) cannot access class com.sun.tools.javac.processing.JavacProcessingEnvironment (in module jdk.compiler) because module jdk.compiler does not export com.sun.tools.javac.processing to unnamed module @0x21bd20ee -> [Help 1]
Example code can be found under java16/lombok_fixed
Preferably use a new version of the dependency which causes the issue. For instance Lombok 1.18.20 includes support for Java 16:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
If there's no new dependency available it's possible to open up the JDK internals, so they can be used. However, this is not the prettiest solution:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<fork>true</fork>
<compilerArgs>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED</arg>
</compilerArgs>
</configuration>
</plugin>
- Update to Gradle 7.3 to get Java 17 support
- Update to Kotlin 1.6.0 to be able to set jvmTarget to 17
Launcher option --illegal-access no longer works to access internal JDK API's.
java.lang.reflect.InaccessibleObjectException: Unable to make … accessible: module java.base does not "opens …" to unnamed module …
- If triggered by a dependency, upgrade the dependency
- Refactor code to no longer use internal JDK API's
- As a last resort use
--add-opens
for instance:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
Mockito raises an exception when Mocking an enum which uses methods in values, such as for example:
public enum ExampleEnum {
TEST {
public String retrieve() {
return "test";
}
};
}
@ExtendWith(MockitoExtension.class)
public class ExampleEnumTest {
@Mock
private ExampleEnum exampleEnum;
@Test
public void testEnumWithMethods() {
assertNotNull(exampleEnum);
}
}
Example code can be found under java17/mockito_fixed
org.mockito.exceptions.base.MockitoException:
Mockito cannot mock this class: class com.example.ExampleEnum.
If you're not sure why you're getting this error, please report to the mailing list.
Java : 17
JVM vendor name : Oracle Corporation
JVM vendor version : 17-ea+24-2164
JVM name : OpenJDK 64-Bit Server VM
JVM version : 17-ea+24-2164
JVM info : mixed mode, sharing
OS name : Linux
OS version : 4.19.76-linuxkit
You are seeing this disclaimer because Mockito is configured to create inlined mocks.
You can learn about inline mocks and their limitations under item #39 of the Mockito class javadoc.
Underlying exception : org.mockito.exceptions.base.MockitoException: Could not modify all classes [class com.example.ExampleEnum, interface java.lang.constant.Constable, class java.lang.Object, interface java.lang.Comparable, interface java.io.Serializable, class java.lang.Enum]
Caused by: org.mockito.exceptions.base.MockitoException: Could not modify all classes [class com.example.ExampleEnum, interface java.lang.constant.Constable, class java.lang.Object, interface java.lang.Comparable, interface java.io.Serializable, class java.lang.Enum]
Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the class NestHost, NestMembers, Record, or PermittedSubclasses attribute
Not yet available, see issue in Mockito GitHub repo, which mentions a workaround.
EqualsVerifier via Bytebuddy
-> Java 21 (65) is not supported by the current version of Byte Buddy which officially supports Java 20 (64) - update Byte Buddy or set net.bytebuddy.experimental as a VM property
Upgrade to a version of Bytebuddy which supports Java 21 or use the workaround:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-Dnet.bytebuddy.experimental=true</argLine><!-- Needed as EqualsVerifier uses Byte Buddy which doesn't support Java 21 -->
</configuration>
</plugin>
An error has occurred in JaCoCo Aggregate report generation. Error while creating report: Error while analyzing … Unsupported class file major version X
Execution default of goal org.pitest:pitest-maven:1.4.10:mutationCoverage failed: Unsupported class file major version 61
Execution repackage of goal org.springframework.boot:spring-boot-maven-plugin:2.2.10.RELEASE:repackage failed: Unsupported class file major version 61
The class file major version 61 is used for Java 17. Make sure your plugins/dependencies are up to date and support Java 17.
- Update your plugins/dependencies
The value of JAVA_HOME
is used by Maven to select the JDK.
Display the current JAVA_HOME
echo $JAVA_HOME
C:\Program Files\AdoptOpenJDK\jdk-16.0.0.36-hotspot
Set JAVA_HOME
to another JDK:
JAVA_HOME="[location of JDK]"
Then use the following Maven command inside one of the Java directories (java11, java16...) to build all submodules and continue when something goes wrong:
mvn compile --fail-at-end -Dmaven.compiler.release=[Specify JDK version. Don't use this on Java 8 as it's not supported]
Or the shorter version:
mvn compile -fae -Dmaven.compiler.release=[Specify JDK version. Don't use this on Java 8 as it's not supported]
The version of the script for Java 8
mvn compile -fae -Dmaven.compiler.target=8 -Dmaven.compiler.source=8
Or change the Maven releases in the pom.xml
:
<properties>
<maven.compiler.release>7</maven.compiler.release>
</properties>
Then use the following Docker command inside one of the Java directories (java11, java16...):
Change the JDK_VERSION to whatever version (11 or greater) you want:
docker build -t javaupgrades -f ..\Dockerfile --build-arg DISABLE_CACHE="%date%-%time%" --build-arg JDK_VERSION=17 --progress=plain .
Or to build on Java 8, which requires a different configuration:
docker build -t javaupgrades -f ..\DockerfileJava8 --build-arg DISABLE_CACHE="%date%-%time%" .
Maven Toolchains can be used to configure the JDK's present on your machine and then select one to use in the pom.xml
of the project.
First create a toolchains.xml
located in ${user.home}/.m2/
<?xml version="1.0" encoding="UTF8"?>
<toolchains>
<toolchain>
<type>jdk</type>
<provides>
<version>8</version>
</provides>
<configuration>
<jdkHome>/path/to/jdk/8</jdkHome>
</configuration>
</toolchain>
<toolchain>
<type>jdk</type>
<provides>
<version>11</version>
</provides>
<configuration>
<jdkHome>/path/to/jdk/11</jdkHome>
</configuration>
</toolchain>
<toolchain>
<type>jdk</type>
<provides>
<version>16</version>
</provides>
<configuration>
<jdkHome>/path/to/jdk/16</jdkHome>
</configuration>
</toolchain>
<toolchain>
<type>jdk</type>
<provides>
<version>17</version>
</provides>
<configuration>
<jdkHome>/path/to/jdk/16</jdkHome>
</configuration>
</toolchain>
</toolchains>
Then in the pom.xml
configure which JDK from the toolchains.xml you want to use:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-toolchains-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<toolchains>
<jdk>
<version>${maven.compiler.release}</version>
</jdk>
</toolchains>
</configuration>
<executions>
<execution>
<goals>
<goal>toolchain</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Make sure to update the Maven Compiler Plugin. When using an older version, combined with the Toolchains Plugin, the errors are not really detailed:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project broken: Compilation failure -> [Help 1]
When using a newer version of the Maven Compiler Plugin the error message provides more detailed information:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.10.1:compile (default-compile) on project lombok_broken: Compilation failure
[ERROR] java.lang.IllegalAccessError: class lombok.javac.apt.LombokProcessor (in unnamed module @...) cannot access class com.sun.tools.javac.processing.JavacProcessingEnvironme
nt (in module jdk.compiler) because module jdk.compiler does not export com.sun.tools.javac.processing to unnamed module @...
Multi release JAR's make it possible to create one JAR file which supports multiple Java versions for backward compatibility.
This example uses records and String.isBlank()
which was introduced in Java 11. The example has two main directories:
- src/main/java used on Java versions below Java 17
- src/main/java17 used by Java version 17 and above
The JAR file for this example should be build on Java 17 and can then be used on various Java versions.
The following command can be used to build the examples on Java 17 and then run them on 8, 11 and 17:
mvn package
run-multi-release-application.cmd
# docker build -t multi-release-jar --build-arg DISABLE_CACHE="%date%-%time%" --progress=plain .
Make sure your code for all Java versions contains the same public API's, else you might run into runtime issues. IntelliJ checks this and Java 17 now contains the jar --validate
option to verify a JAR file. Build tools like Maven don't verify it automatically.