Managing Multiple JDK Installations With jEnv

  • October 18, 2022
Table Of Contents

As developers, we’re often working on different codebases at the same time. Especially in environments with microservices, we may be switching codebases multiple times a day.

In the days when a new Java version was published every couple of years, this was often not a problem, because most codebases needed the same Java version.

This changed when the Java release cadence changed to every 6 months. Today, if we’re working on multiple codebases, chances are that each codebase is using a different Java version.

jEnv is a tool that helps us to manage multiple JDK installations and configure each codebase to use a specific JDK version without having to change the JAVA_HOME environment variable.

Make sure to check out the article about SDKMAN!, an alternative tool for managing JDKs (and other tools).

Installing jEnv

jEnv supports Linux and MacOS operating systems. If you’re working with Windows, you’ll need to install the Windows Subsystem for Linux (or a bash emulator like GitBash) to use it.

Follow the installation instructions on the jEnv homepage to install jEnv.

Installing a JDK

If you’re reading this article, chances are that you want to set up a new JDK for a codebase you’re working on. Let’s download a JDK from the AdoptOpenJDK website.

Choose the version you want and download it. Extract the .tar.gz file wherever you want.

The good thing about jEnv is that we don’t need to install the JDK via a package manager like brew, yum, or apt. We can just download a JDK and put it into a folder somewhere.

You can still use brew, yum, or apt to install your JDKs, you just need to find out the folder where your package manager has put the JDK afterward.

Adding a JDK to jEnv

To use the new JDK with jEnv, we need to tell jEnv where to find it. Let’s check first which versions of the JDK jEnv already knows about with the command jenv versions:

* system (set by /home/tom/.jenv/version)
  11
  11.0
  11.0.8
  13
  13.0
  13.0.2
  14
  14.0
  14.0.2
  openjdk64-11.0.8
  openjdk64-13.0.2
  openjdk64-14.0.2

In my case, I have the JDKs 11, 13, and 14 already installed. Each version is available under three different names.

Let’s say we’ve downloaded JDK 15 and extracted it into the folder ~/software/java/jdk-15+36.

Now, we add the new JDK to jEnv:

jenv add /home/tom/software/java/jdk-15+36/

If we run jenv versions again, we get the following output:

  11
  11.0
  11.0.8
  13
  13.0
  13.0.2
  14
  14.0
  14.0.2
  15
  openjdk64-11.0.8
  openjdk64-13.0.2
  openjdk64-14.0.2
  openjdk64-15

The JDK 15 has been added under the names 15 and openjdk64-15.

Local vs. Global JDK

jEnv supports the notion of a global JDK and multiple local JDKs.

The global JDK is the JDK that will be used if we type java into the command line anywhere on our computer.

A local JDK is a JDK that is configured for a specific folder only. If we type java into the command line in this folder, it will not use the global JDK, but the local JDK instead.

We can use this to configure different JDKs for different projects (as long as they live in different folders).

Setting the Global JDK

First, we check the version of the global JDK:

jenv global

The output in my case is:

system

This means that the system-installed JDK will be used as a global JDK. The name system is not very helpful because it doesn’t say which version it is. Let’s change the global JDK to a more meaningful JDK with a version number:

jenv global 11

This command has changed the globally used JDK version to 11. In my case, this was the same version as before, but if I type jenv global, I will now see which JDK version is my global version.

Setting the Local JDK

Remember the JDK 15 we’ve downloaded? The reason we downloaded it is probably that we’re working on a new project that needs JDK 15 to run.

Let’s say this project lives in the folder ~/shiny-project. Let’s cd into this folder.

If I type java -version now, I get the following result:

openjdk version "11.0.8" 2020-07-14
OpenJDK Runtime Environment (build 11.0.8+10-post-Ubuntu-0ubuntu118.04.1)
OpenJDK 64-Bit Server VM (build 11.0.8+10-post-Ubuntu-0ubuntu118.04.1, mixed mode, sharing)

That is because JDK 11 is my global JDK.

Let’s change it to JDK 15 for this project:

jenv local 15

Now, type java -version again, and the output will be:

openjdk version "15" 2020-09-15
OpenJDK Runtime Environment AdoptOpenJDK (build 15+36)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 15+36, mixed mode, sharing)

Calling java in this folder will now always call Java 15 instead of Java 11.

How does this work?

After using the jenv local command, you’ll find a file called .java-version in the current folder. This file contains the version number of the local JDK.

During installation, jEnv overrides the java command. Each time we call java now, jEnv looks for a .java-version file and if it finds one, starts the JDK version defined in that file. If it doesn’t find a .java-version file, it starts the globally configured JDK instead.

Working with Maven and Gradle

So, if we call java via the command line, it will pick up a locally configured JDK now. Great!

But tools like Maven or Gradle still use the system version of the JDK!

Let’s see what we can do about that.

Configure jEnv to Work With Maven

Making Maven work with the local JDK defined by jEnv is easy. We just need to install the maven plugin:

jenv enable-plugin maven

If we run mvn -version in our ~/shiny-project folder from above now, we’ll get the following output:

Maven home: .../apache-maven-3.6.3
Java version: 15, vendor: AdoptOpenJDK, runtime: /home/tom/software/java/jdk-15+36
Default locale: en_AU, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-52-generic", arch: "amd64", family: "unix"

Maven is using the new JDK 15 now. Yay!

Configure jEnv to Work With Gradle

In my case, Gradle picked up jEnv’s locally configured JDK automatically!

If it doesn’t work out of the box for you, you can install the gradle plugin analogously to the Maven plugin above:

jenv enable-plugin gradle

If we run gradle -version in our ~/shiny-project folder from above now, we’ll get the following output:

------------------------------------------------------------
Gradle 6.5
------------------------------------------------------------

Build time:   2020-06-02 20:46:21 UTC
Revision:     a27f41e4ae5e8a41ab9b19f8dd6d86d7b384dad4

Kotlin:       1.3.72
Groovy:       2.5.11
Ant:          Apache Ant(TM) version 1.10.7 compiled on September 1 2019
JVM:          15 (AdoptOpenJDK 15+36)
OS:           Linux 5.4.0-52-generic amd64

Not Picking the Right Java Version?

Depending on your context, jenv might not pick the right Java version and you might end up with errors that complain about Java incompatibilities even though you have set a local Java version using jenv local correctly.

In this case, you might need to enable the export plugin, which sets the JAVA_HOME variable properly:

jenv enable-plugin export

Now, when you run a command like ./gradlew ... or ./mvnw ..., it should pick the correct Java version.

More troubleshooting tips can be found on the official troubleshooting page.

Conclusion

jEnv is a handy tool to manage multiple JDK versions between different projects. With jenv local <version> we can configure a JDK version to be used in the current folder.

Written By:

Tom Hombergs

Written By:

Tom Hombergs

As a professional software engineer, consultant, architect, general problem solver, I've been practicing the software craft for more than fifteen years and I'm still learning something new every day. I love sharing the things I learned, so you (and future me) can get a head start. That's why I founded reflectoring.io.

Recent Posts

Guide to JUnit 5 Functional Interfaces

In this article, we will get familiar with JUnit 5 functional interfaces. JUnit 5 significantly advanced from its predecessors. Features like functional interfaces can greatly simplify our work once we grasp their functionality.

Read more

Getting Started with Spring Security and JWT

Spring Security provides a comprehensive set of security features for Java applications, covering authentication, authorization, session management, and protection against common security threats such as CSRF (Cross-Site Request Forgery).

Read more

Creating and Publishing an NPM Package with Automated Versioning and Deployment

In this step-by-step guide, we’ll create, publish, and manage an NPM package using TypeScript for better code readability and scalability. We’ll write test cases with Jest and automate our NPM package versioning and publishing process using Changesets and GitHub Actions.

Read more