Between Two Parens

Manage your Java JDKs

posted on

Pro tip for anyone working with any programming language: seek out a language version management tool.

All it takes is a CI environment running a different version of your programming language and the world goes Pete Tong.

It's for this reason that I always manage my programming languages with a language version manager. If you're using JavaScript, nvm is a great option, Ruby has rvm and for Clojure developers we have tools.deps for managing versions of Clojure and jEnv for managings our JDKs.

This post will cover the following topics:

Why Version Management Tools

With every line of code we write, we're making assumptions. We assume that our code will run in specific environment(s) and work in a particular way. The longer we play the game, the clearer we see that these assumptions are traps.

A possible solution is to agree on standards. We say that our software will work on specific versions of a language and in specific environments. Then we work to back this guarantee. There is also another side to this coin. Something unexpected happens and our standards are betrayed and we have to figure out why.

It's for these reasons that we have language version management tools. They allow us to quickly and easily manage (add, switch, remove et al.) the versions of our programming languages. This enables us to better debug and fortify our code. Example scenarios where I've had to switch language versions:

  • Debugging CI environments
  • Resolving "works on my machine" events
  • Regression testing an app or library
  • Switching between multiple projects with different language version requirements

As you can imagine, this is a common software development problem and for every language it's handled in a different, but similar way. In the case of Clojure, there two things we have to manage: versions of Clojure and versions of the JDK. This post is going to focus on managing versions of the JDK with the help of jEnv.

jEnv is a tool that allows you to manage your Java JDK installations. It's focus is on allowing you to easily switch between versions of the JDK. One thing to note is that this tool does not allow you to install a JDK. You have to do this separatley. Further, while the process to setup jEnv is straightforward, there are some gotchas which is why i'm writing this guide.

Installing jEnv

The first thing we have to do is install jEnv.

brew install jenv

and now let's do a sanity check to see if it's installed correctly. Run:

jenv doctor

and if the above worked you should see something like this:

[ERROR]	Java binary in path is not in the jenv shims.
[ERROR]	Please check your path, or try using /path/to/java/home # ...
[ERROR]	Jenv is not loaded in your zsh
[ERROR]	To fix : cat eval "$(jenv init -)" >> /Users/# ...

Assuming the above worked, we can move onto actually configuring jEnv. This means that the next step is to add jEnv to our PATH.

To do this, we have to add some code to our shell configuration file. Now, if you'r not sure which shell you're running, that's fine, I will assume we don't know. This means that our next step is to find out which shell we are using is run the following command:

echo $0

If you are using bash, the above will print out -bash and if you're using zsh the above will print out -zsh. Once you discover which shell you are using, please choose the associated code block below and run the code inside of it line by line:

zsh shell

echo 'export PATH="$HOME/.jenv/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(jenv init -)"' >> ~/.zshrc

bash shell

echo 'export PATH="$HOME/.jenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(jenv init -)"' >> ~/.bashrc

Now we can once again run a sanity check:

jenv doctor

Which will return something like this:

[OK]	No JAVA_HOME set
[ERROR]	Java binary in path is not in the jenv shims.
[ERROR]	Please check your path, or try using /path/to/java/home #...
[OK]	Jenv is correctly loaded

As you can see, we now have two less errors. We're making progress! From here, we want to run the following:

jenv enable-plugin export
exec $SHELL -l

and now running jenv doctor returns this:

[OK]	JAVA_HOME variable probably set by jenv PROMPT
[ERROR]	Java binary in path is not in the jenv shims.
[ERROR]	Please check your path, or try using /path/to/java/home #...
[OK]	Jenv is correctly loaded

again, the output has changed indicating that we are moving along. At this point, jEnv is setup and the last item on our list is to install a Java JDK. If you don't have one installed checkout AdoptOpenJDK

Add a JDK to jEnv

Not sure if you have a Java JDK installed? No worries, we will find out together! What we can do is run the following command which is going to tell us where our Java JDK's are installed. So, run this command:

/usr/libexec/java_home -V

and that will print something like this:

Matching Java Virtual Machines (2):
    13.0.2, x86_64:	"AdoptOpenJDK 13"	/Library/Java/JavaVirtualMachines/adoptopenjdk-13.jdk/Contents/Home
    11.0.6, x86_64:	"AdoptOpenJDK 11"	/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home

If you see something like the above, where your JDK's are listed out, it means you have one installed and can continue on with this section.

Now we can go ahead and add JDK's to jEnv so it can manage them for us. Run the following command

jenv add /Library/Java/JavaVirtualMachines/adoptopenjdk-13.jdk/Contents/Home

When you run the above, you will see something like this returned:

openjdk64-13.0.2 added
13.0.2 added
13.0 added

As a sanity check, we can run jenv versions to see if jEnv knows about our JDK:

* system (set by /Users/thomasmattacchione/.jenv/version)
  13.0
  13.0.2
  openjdk64-13.0.2

Great, but remember how I have two JDK's installed? The above indicates that only one of them is being managed by jEnv. This means I have to run through the above step again to have jEnv know about the other version of the JDK I have installed. So, let's repeat those steps:

jenv add /Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home

again we get our success output

openjdk64-11.0.6 added
11.0.6 added
11.0 added

and when we check versions available now by running jenv versions we have both 11 and 13 available to us:

* system (set by /Users/thomasmattacchione/.jenv/version)
  11.0
  11.0.6
  13.0
  13.0.2
  openjdk64-11.0.6
  openjdk64-13.0.2

Set a JDK version via jEnv

As a final step, we want to actualy set a JDK to be the one we use. You have two options for this: jenv local and jenv global.

jenv local is going to set your JDK of choice for your current terminal session. jenv global is going to set your chosen JDK as the default for all terminal sessions. For now, let's set a global version. In my case, i'm going to set my jenv global to AdoptOpenJDK 11 like this:

jenv global 11.0.6

Now, in order for the above to take effect, we have to open a new terminal window. Once you open a new terminal window run the following command to be sure our specified version of Java was set:

java -version

and you should see something like this:

openjdk version "11.0.6" 2020-01-14
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.6+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.6+10, mixed mode)

That's everything involved in setting a global version of JDK through jEnv.

Uninstalling Jenv

If for any reason you feel like something went wrong while installing jenv, or maybe you just don't like jEnv and you want to cleanup your environment the following steps will help you remove it.

brew uninstall jenv

Remove the .jenv directory

rm -rf ~/.jenv

remove the PATH and init script we added to our shell (either bash or zsh)

That's everything involved in setting up and working with Jenv. I hoped this helped.