Managing Java Versions on a Mac

Intro

I am told that I need to run Java 11 on my machine. When I run java -version, it reads:

❯ java -version
java version "1.8.0_333"
Java(TM) SE Runtime Environment (build 1.8.0_333-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.333-b02, mixed mode)

WTF does that mean?! Is this Java 8 or 1.8? How can I switch between versions? Is there a Java version manager (à la nvm)?

Java vs Javac vs JDK vs JRE vs JVM

  • javac: the executable that compiles your source code into byte code (i.e. it converts .java files into .class files), and links it with other .class files within your JDK library.
  • java: the command-line binary executable that creates a process based on the content of the JVM and byte code of the .class files specific to the program you are running
  • JVM: instructions that are needed to interpret byte code in the programs .class files. (It’s not clear to me what format the JVM consists of on disk; I am told it is itself in byte code on disc).
  • JRE: the minimum stuff needed to execute java programs, but not in general what is needed to create them. (Note: the diagram below is misleading since it suggests that the JRE does not include java, which it certainly does!)
  • JDK: the set of all tools and libraries to both run and create Java programs.
Java overview

Java Versions

You do not just install “java”, you install either a JDK or a JRE that includes the java executable. (Most machines will come with a built-in JRE; anything you install will, in practice, almost always be a JDK so that you can make — not just run — Java programs.)

When it comes to versioning, the java executable is inseparable from the JRE/JDK it was installed with. (It’s not like java has version X, and the JVM it comes with is version Y — they are both part of the same bundle as far as versioning is concerned.)

The way that Java versions are numbered has suffered from a historic lack of consistency. In short, for Java versions up to 8 the output of java -version would look like java version "1.8.0_333", and from the Java 9 onwards the output looks like openjdk version "9.0.1".

Java Built-Into MacOS

MacOS has a built-in version of Java 8. As you can imagine, it’s super important to the overall running of the Mac, so you don’t want to mess with it. MacOS also comes with a command-line tool that finds installations:

❯ /usr/libexec/java_home -V
Matching Java Virtual Machines (2):
    1.8.333.02 (x86_64) "Oracle Corporation" - "Java" /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home
    1.8.0_191 (x86_64) "Oracle Corporation" - "Java SE 8" /Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home

It indicates that two JVMs are installed and provides the locations of dirs named Home for each. (It’s not clear if both of these Java 8 installations came built-into the OS, or whether one was added by me directly or indirectly later.)

Java Installed via Homebrew

To add e.g. Java 11, use brew install openjdk@11. To add the latest version, use brew install openjdk.

Note: homebrew installations of Java will not set up paths for you automatically, and the /usr/libexec/java_home tool does not locate them!

JAVA_HOME

Lets call the directory where a JRE or JDK is installed to for a particular version the JRE/JDK the “Home” directory for that versioned installation of Java. The Home will always have a lib and a bin dir, as well as other stuff (in general). java will always be in bin, and if it is a JDK, then bin will also have javac.

If you try to run java from a given Home, then I expect it will automatically search for the JVM and library files within that same Home. However, to be sure that the Java-related process you are running will look for binaries, libraries, etc., in a specific Home, you can set the JAVA_HOME env var to the path of that Home dir.

At this time, I have 4 such Homes on my Mac (2 seemingly built-into the MacOS; 2 installed via homebrew). They are located at:

JAVA_8_HOME="/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home"
JAVA_8_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home"
JAVA_11_HOME="/usr/local/Cellar/openjdk@11/11.0.12/libexec/openjdk.jdk/Contents/Home"
JAVA_18_HOME="/usr/local/Cellar/openjdk/18.0.1/libexec/openjdk.jdk/Contents/Home"

Notice that the JDK/JRE are installed to a dir named “Home”. That is because the env variable JAVA_HOME is intended to be set to one of these Home directories. So if you want to be sure to run or build a program with, say, Java 11, then you can be explicit with this sort of syntax:

JAVA_HOME=$JAVA_11_HOME $JAVA_11_HOME/bin/java ...... 

Maven

Install maven with brew install maven. When maven runs, it will need to access the contents of a JDK/JRE, that is, it will need to look in one of these Home dirs for a versioned installation of Java. Running maven -version will show you what Java version it is using at the moment.

If you want to ensure that maven uses a specific version of Java, then use similar syntax to above; e.g.:

JAVA_HOME=$JAVA_11_HOME mvn -version

In this example, mvn will look in the dir $JAVA_11_HOME for all of its Java stuff, and thereby “use Java 11”. I confirmed that this works on a complex project that requires Java 11 specifically.

Maven itself has its own versioning; I would not be surprised if older version of Maven generally work better when directed to work with older version of Java.

Summary

Understanding Java versions is fairly straightforward once you understand the historic convention for Java 1-8 vs Java 9 onwards. Switching between versions is a matter of setting JAVA_HOME to point to the Home dir of a specific JRE/JDK versioned installation on your filesystem. There is no nvm for Java, AFAIK.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *