Alright, real talk: I finished my first Java project as a like, totally professhional developer without having much of a clue about, well, anything to do with the Java build/tooling ecosystem. I pressed enough buttons in IntelliJ for things to run, and (thankfully) they ran, and I spent the rest of the project perpetually swaddled within IntelliJ's gentle embrace.
Winging it? Nah, I call it falling with style.
Basically, I found comprehending Java tooling seriously perplexing. So before kicking off my second Java project I wanted to forge a better understanding of just what the heck was going on.
Over the course of a few posts we're going to learn a little more about how Java is installed on our local machine before building a straightforward (and only somewhat contrived) Hello World
application, making use of external libraries and multiple files so we can make some sense of a standard Java directory structure and how to build our application from the command line. Then we'll move on to incorporating the same app with help from Gradle, a popular build tool, and we'll finish by incorporating our project into the IntelliJ IDE. At the end of this series we should all have a much more comfortable knowledge of what goes on under the hood of all our tooling, and way more confidence to tinker with our project builds.
First steps: the absolute basics. Time for a forensic journey. The first time I quickly fudged my way through installing OpenJDK 12 and I can't even tell you how I did it. Downloading OpenJDK, as far as I can tell, gives you a directory of unloved binaries which virtually zero guidance on what you're supposed to do with them. I felt like Ted Danson Steve Guttenberg in Three Men and a Baby.
**tl;dr:* copy the uncompressed OpenJDK directory - e.g. jdk-12.jdk
- into /Library/Java/JavaVirtualMachines/
and you'll be good.*
But whyyyy? Let's unmangle that.
Caveat: this is specific to macOS right now. Linux and Windows will inevitably be different, and likely have a swathe of gremlins of their very own.
Where is Java actually installed?
Strap in, because descending through an installation of Java is like staring Cthulhu in the face for a fortnight. Descending into its heart of darkness is a guarantee that you're going to end up as Kurtz. You've been warned.
Let's start at the top:
$ java --version
openjdk 12 2019-03-19
OpenJDK Runtime Environment (build 12+33)
OpenJDK 64-Bit Server VM (build 12+33, mixed mode, sharing)
And then we can peek inside our java.home
system property from within jshell
.
jshell> System.getProperty("java.home")
$1 ==> "/Library/Java/JavaVirtualMachines/jdk-12.jdk/Contents/Home"
Alright, cool. We've got Java installed. It's the latest version, and it's blissfully not the Oracle JDK. Things are looking rosy. So presumably our shell will point to our java.home
directory?
$ which java
/usr/bin/java
OK. But that file is 74 bytes, which if you ask me a tell-tale sign a symbolic link. This ain't our first rodeo!
$ greadlink -f $(which java)
/System/Library/Frameworks/JavaVM.framework/Versions/A/Commands/java
Well, that's new. And if we have a peep within the JavaVM.framework
directory we can see all our familiar Java files.
$ ls -lh /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/ | grep "java"
-rwxr-xr-x 1 root wheel 57K 21 Mar 06:07 java
-rwxr-xr-x 1 root wheel 74K 21 Mar 06:07 java_home
-rwxr-xr-x 1 root wheel 57K 21 Mar 06:07 javac
-rwxr-xr-x 1 root wheel 57K 21 Mar 06:07 javadoc
-rwxr-xr-x 1 root wheel 57K 21 Mar 06:07 javah
-rwxr-xr-x 1 root wheel 57K 21 Mar 06:07 javap
-rwxr-xr-x 1 root wheel 57K 21 Mar 06:07 javapackager
-rwxr-xr-x 1 root wheel 56K 21 Mar 06:07 javaws
These aren't symbolic links, so that's good. Things still don't seem to be lining up, mind. But a little more digging on Stack Overflow suggests these are stub applications which determine the version of the JVM to use. Have you ever seen anything like that before? Because I haven't.
Some more internet trawling reveals java_home
, which according to its man
page:
returns a path suitable for setting the JAVA_HOME environment variable. It determines this path from the user's enabled and preferred JVMs in the Java Preferences application. Additional constraints may be provided to filter the list of JVMs available. By default, if no constraints match the available list of JVMs, the default order is used. The path is printed to standard output.
Only I'm fairly sure Apple removed the Java Preferences application yonks ago, but no matter. Did they just sort of half-remove the Java support from the OS, keeping us lightly tethered to an unloved, unmaintained ghost in the machine? That's an open question: I have no idea. I only know we wouldn't be Java developers if we cared for such consistencies. Let's just boot it up.
$ /usr/libexec/java_home
/Library/Java/JavaVirtualMachines/jdk-12.jdk/Contents/Home
Alright! Fantastic. We finally got to /Library/Java/JavaVirtualMachines
. I've only lost part of my sanity, so I'll take this as a win. java_home
seems to prioritise more recent versions of the JDK by default, so if you've got 12 installed it will supersede 11 when you run java
, javac
, jshell
and the like. You can look at all your currently installed versions with the -V
(capitalised) flag.
$ /usr/libexec/java_home -V
Matching Java Virtual Machines (2):
12, x86_64: "OpenJDK 12" /Library/Java/JavaVirtualMachines/jdk-12.jdk/Contents/Home
11, x86_64: "OpenJDK 11" /Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home
/Library/Java/JavaVirtualMachines/jdk-12.jdk/Contents/Home
Meanwhile, the -v
(lower case) flag will return the path to said JVM, which can then be set as the JAVA_HOME
environment variable. JAVA_HOME
, when set, takes precedence over the default ordering of /usr/libexec/java_home
.
$ export JAVA_HOME=$(/usr/libexec/java_home -v 11)
$ java --version
openjdk 11 2018-09-25
OpenJDK Runtime Environment 18.9 (build 11+28)
OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)
Want to relive the halcyon days of JDK 1.6? Go right ahead. Have a blast. No, I'm OK - I'll stick around here, cheers. Of course, running the export
command in your shell will only last the lifetime of that shell instance. For a more permanent solution you'll need to nip into your .bashrc
/.zshrc
or equivalent.
I'm assuming that the default choices of /usr/libexec/java_home
mirror what happens when you run one of the stub applications in /System/Library/Frameworks/JavaVM.framework/
. Or, you might have spotted that there was also a java_home
binary in the JavaVM.framework
directory earlier. Wait a second...
$ greadlink -f /usr/libexec/java_home
/System/Library/Frameworks/JavaVM.framework/Versions/A/Commands/java_home
Look, I did warn you that it would be messy. But at least now we know a lot more about how Java is running on our system!
In the next post we'll start looking at the challenges of compiling and running our first Java application. I'll update this post with a link when it's live!
Has this post been useful for you? I'd really appreciate any comments and feedback on whether there's anything that could be made clearer or explained better. I'd also massively appreciate any corrections!
Top comments (4)
Why not use homebrew?
That is an excellent point! I needed to
brew cask install adoptopenjdk
to get the latest version, but it's definitely a faster way to quick install.It does install itself into the same directory structure, though, so if anyone is anything like me (they like taking the long road...) they might find it interesting.
I will update the post to reflect that - thanks for pointing it out :)
Hey Martin, I just stumbled across this and thought you'd find this useful too: sdkman.io/
That's awesome! Thanks for sharing. I'm going to give this a whirl this weekend ð