Many users have already upgraded from Java 7 to Java 8, to benefit from improvements in speed, brevity of code, and lower memory usage. Other users have asked for more prescriptive guidance of the upgrade: when to make the change, what to expect, and how to do it in a controlled manner.
Relation to a previous post
A previous post, Upgrading Major Java Versions, provides details for certain stakeholders: support timelines, compatibility guides, lists of changes, and different supporting material.
This post is intended to provide more prescriptive guidance of upgrading your Java SE version: how to test components and features designed to control behavior and upgrade part of your environment in stages.
The decision to upgrade is not only for companies that develop software; it also applies to users running software built by others. In many cases, users can see significant speed improvements without recompiling, simply by upgrading the runtime.
Planning upgrade in stages
The previous post explains when to upgrade in relation to platform support. When upgrading infrastructure, it is important to segment the architecture. Rather than upgrading everything at the same time, separate it into different environments so that you can test each one on its own.
Typical environments, in my preferred order of when to upgrade:
- Developer workstations, where developers write and test code. This is most likely where you would run IDEs like NetBeans, Eclipse, or IntelliJ.
- Central build servers, where code is combined, built, and unit tested through automation. Common software is Hudson or other continuous integration software. Some organizations do not have central build servers.
- Test or QA servers. This environment may run your application in order to find any issues before release into production.
- Production servers. The final environment that should be upgraded last, these servers run your application for users.
High level upgrade plan: Upgrade the build and test environments but keep things targeted for production. Once you are ready, upgrade the production environment and begin taking advantage of new features.
The JDK is backwards compatible, meaning that JDK 8 can run binaries produced by JDK 7 and JDK 6. The javac compiler can also produce binaries targeting earlier versions of the JDK. In planning to upgrade, we will use this capability.
Upgrading Developer Workstations and/or Central Build Servers
The important similarity between developer workstations and central build servers is that they are used to compile the application from source code into binary artifacts, such as JAR and WAR.
Upgrading a workstation or build server involves upgrading its JDK installation. The same system may run multiple versions of the JDK side-by-side, making it your choice if you want to uninstall the older one.
Environment Variables for installation
If you choose to run multiple versions, just be mindful of two environment variables:
- PATH – identifies which actual java executable your system will use when you call java. You can circumvent this by explicitly calling the executable via /opt/jdk8/bin/java or /opt/jdk7/bin/java commands. Just call the one you want to use at the time. If you use scripts that rely on environment variables, consider isolating your terminals if you change the environment.
- JAVA_HOME – some applications use this variable to identify where Java is.
Test your upgrade the following commands:
- java -version
- This should reply back with 1.8 or 1.7 depending on which you want. Just make sure it is right
- echo $JAVA_HOME
- On Linux, that will identify the JAVA_HOME variable that some applications may use. Check that it is the installation you intend to use.
- On Windows, use: echo %JAVA_HOME%
- You can also check the entire process with:
- Linux: $JAVA_HOME/bin/java -version
- Windows: %JAVA_HOME%\bin\java -version
Personal Tip: On my Windows 7 laptop, I regularly switch Java version to test things. To counter forgetfulness, I set my JAVA_HOME variable first, and then my PATH uses that JAVA_HOME. By doing this, I only need to change one thing. My PATH starts with: %JAVA_HOME%\bin;
Cross-compiling to meet your runtime’s compatibility
By using your upgraded installation to cross-compile, you will produce artifacts that run in your not-yet-upgraded test and production environments.
The javac compiler provides three options to control compatibility: -bootclasspath -source and -target. Without the -source flag, the compiler won’t warn you about using language features that may not be available on your earlier target JDK platform. Without the -target flag, the compiler won’t produce binaries that can run on your earlier target JDK. Finally, without the -bootclasspath flag, the compiler won’t be able to find the correct version of the core class libraries from the earlier JDK. A simple example of using all flags correctly can be found in the javac documentation’s Examples section.
Configure the build to specify the -source and -target of your runtime.
- Regular javac: -source 1.X -target 1.X
If you have the older JDK: javac: -source 1.X -target 1.X –bootclasspath JDK1.6/lib/rt.jar
- Maven: Modern versions of maven have a <maven.compiler.source>1.X</maven.compiler.source> attribute that can be set in properties. Alternately, use the maven-compiler-plugin attributes like <source/> and <target/>
- Ant: The <javac source="1.X" target="1.X" /> task, or you can use a separate ant.build.javac.source property.
- Etc. consult your build system’s documentation.
Monitor compiler errors and warnings
Building your application with the latest JDK will identify any potentially problematic areas. Investigate compiler errors, if any.
Although compiler warnings do not cause build failures, they indicate areas of interest. Looking into them provides a safeguard of something that may affect compatibility or legibility of code.
In JDK 8, we added special indicators to point where incompatibilities may occur in the future.
com/example/App.java:[32,24] SOMETHING is internal proprietary API and may be removed in a future release
In this example, my code has made use of a JDK internal API on line 32 of App.java. If you see this message, your code will likely still work for now but you should consider moving towards the known-compatible replacement for that API.
Use jdeps or other compatibility analyzers
OpenJDK 8 features a new dependency analysis tool, jdeps, designed to identify where applications or their dependencies make use of internal JDK APIs. Usage of these APIs does not currently indicate incompatibility, rather they point out where you use non-public and unsupported internal APIs.
If jdeps identifies usage in your code, consider switching to the public replacement APIs. If jdeps identifies usage in third party code, you may still be impacted in the future. Consider upgrading those identified dependencies, patching them, or identifying an alternative.
We have previously limited access to some internal APIs in some situations. The publicly supported APIs are still available, unaffected, and fine to use.
Consider additional tools for analysis as well:
- The Forbidden APIs project also helps identify cases where certain APIs either should not be used or should be used differently. In addition to identifying internal APIs, this finds potential locale and encoding issues.
- The Modernizer Maven Plugin also locates legacy APIs. While jdeps is focused solely on internal JDK APIs, this finds them in other projects. That may be useful if you are upgrading those other projects as well.
Running an application in test or production
Upgrading the test and production environments will allow you to evaluate an application and see how it interacts with other systems.
The JDK is designed to be backwards compatible. Upon upgrading the test environment, it is your choice if you want to run the same cross-compiled binary.
When testing your application, it is important not to change too many things at once. For example if you test the upgrade while simultaneously testing a complete rewrite of major components at the same time, it will be hard to tell which issue came from which cause. Given time available for testing, is may not be feasible to test the upgrade alone, without any other changes. Isolate changes as best you can and do not pre-assign a root cause to any issue before investigation.
Once you have successfully upgraded your environment(s), there is no more reason to cross-compile your binaries. Consider going back to your build environment and removing the -bootclasspath, -source, and -target flags.
The previous post, Upgrading Major Java Versions, provides
links to detailed information and compatibility guides about what changed
between different versions.
In the interest of brevity, I will list a few noteworthy differences that I have seen:
- JDK 8 uses TLS 1.2 as its default transport protocol for connections like https. JDK 7 made TLS 1.2 available but did not use it as the default. JDK 6 did not offer this protocol. For details, see JDK 8 Will use TLS 1.2 as its default and Diagnosing TLS, SSL and HTTPS.
- JDK 8 no longer has a type of memory called PermGen, as it was replaced by Metaspace. This should not affect most people, but older startup scripts may have used -XX:MaxPermSize options. This should not cause problems, as tuning PermGen will no longer do anything. Please consider removing unnecessary startup options as a good measure.
- Startup switches that begin with -XX: should be validated to see if they still apply. The java command documentation identifies these as advanced options not recommended for casual use. They are subject to change. If you experience unexpected behavior or slower performance, your flags may be working around a problem that no longer exists.
Uptake of Java SE 8
Many applications are able to leverage a smooth upgrade path from JDK 7 to JDK 8, in order to benefit from improvements like speed and more concise code. Examples of teams that have successfully migrated include:
I will continue monitoring different areas and will try to follow up in the future with different strategies for upgrading.