The Fastest Way to get the Version of a Java Installation

Last week, I demonstrated that OpenJDK is faster than GraalVM Java, at least for obtaining the Java version. This even prompted the mighty Thomas Wuerthinger (creator of GraalVM) to react. But the measured ~20ms for the OpenJDK is still too slow for applications like execjar, where it could significantly increase the runtime of short-running CLI tools. In this week’s brief blog post, I’ll show you the fastest way to access the Java version.

The main performance issue is that calling java -version creates a process with a fairly large (around 38MB) maximum resident set size, and using a proper command line parser. But do we actually need to call the java binary to get the version?

TL;DR: I created the java-version tool, which can obtain the Java version in under a millisecond.

Basic Idea

No, we can just realize that most Java installations have a release file that contains the relevant information in a machine-readable format. You can find this file in the main folder of the installation (./release when java is in ./bin).

For my SapMachine 25 installation, the file looks like:

IMPLEMENTOR="SAP SE"
IMPLEMENTOR_VERSION="SapMachine"
JAVA_RUNTIME_VERSION="25+36-LTS"
JAVA_VERSION="25"
JAVA_VERSION_DATE="2025-09-16"
LIBC="default"
...

The file is generated in the OpenJDK at build time via (source):

define create-info-file
  $(if $(JDK_ARCH_ABI_PROP_NAME), \
    $(call info-file-item, "SUN_ARCH_ABI", "$(JDK_ARCH_ABI_PROP_NAME)"))
  $(call info-file-item, "SOURCE", "$(strip $(SOURCE_REVISION))")
  $(call info-file-item, "IMPLEMENTOR", "$(COMPANY_NAME)")
  $(if $(VENDOR_VERSION_STRING), \
    $(call info-file-item, "IMPLEMENTOR_VERSION", "$(VENDOR_VERSION_STRING)"))
  $(call info-file-item, "JAVA_VERSION_DATE", "$(VERSION_DATE)")
  $(call info-file-item, "JAVA_RUNTIME_VERSION", "$(VERSION_STRING)")
  $(call info-file-item, "OS_NAME", "$(RELEASE_FILE_OS_NAME)")
  $(call info-file-item, "OS_ARCH", "$(RELEASE_FILE_OS_ARCH)")
  $(call info-file-item, "LIBC", "$(RELEASE_FILE_LIBC)")
endef

For GraalVM, the file looks similar.

Let’s implement a java-version tool with this knowledge.

Implementation

I chose to implement the tool in C++ to avoid the overhead of any runtime. The implementation is fairly straightforward, especially with the modern filesystem API (C++ 17):

#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>

namespace fs = std::filesystem;

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " <path-to-bin-java>\n";
        return 1;
    }

    // Resolve the path, following any symbol links
    fs::path javaPath;
    try {
        javaPath = fs::canonical(argv[1]);
    } catch (const fs::filesystem_error& e) {
        std::cerr << "Failed to resolve path: " << e.what() << "\n";
        return 1;
    }

    // Expect .../bin/java
    if (javaPath.filename() != "java" ||
        javaPath.parent_path().filename() != "bin") {
        std::cerr << "Path does not end with /bin/java\n";
        return 1;
    }

    // ../release
    fs::path releasePath = javaPath.parent_path().parent_path() / "release";

    // Try to open the release file
    std::ifstream file(releasePath);
    if (!file) {
        std::cerr << "Failed to open " << releasePath << "\n";
        return 1;
    }

    const std::string key = "JAVA_VERSION=\"";
    std::string line;

    // Look for the JAVA_VERSION line
    while (std::getline(file, line)) {
        if (line.rfind(key, 0) == 0) { // starts with key
            auto start = key.size();
            auto end = line.find('"', start);
            if (end != std::string::npos) {
                std::cout << line.substr(start, end - start) << "\n";
                return 0;
            }
        }
    }

    std::cerr << "JAVA_VERSION not found\n";
    return 1;
}

You can find the whole file on GitHub and build it via:

g++ -std=c++17 java_version.cpp -o java_version

Usage

The usage is pretty simple:

./java-version `which java`
# or directly
./java-version java/25-sapmchn/bin/java

Benchmarks

The most important part of this blog post is, of course, the comparison with java -version (although you already saw it in the blog post OpenJDK is faster than GraalVM Java*). For the benchmark, I used hyperfine on my MacBook Pro M5:

Benchmark 1: java -version
  Time (mean ± σ):      19.8 ms ±   1.5 ms    [User: 11.1 ms, System: 10.7 ms]
  Range (min … max):    17.1 ms …  31.1 ms    1000 runs
 
Benchmark 2: java-version java/25-sapmchn/bin/java
  Time (mean ± σ):     925.1 µs ± 418.2 µs    [User: 466.3 µs, System: 373.8 µs]
  Range (min … max):   425.3 µs … 5137.1 µs    1000 runs
 
Summary
  java-version java/25-sapmchn/bin/java ran
   21.41 ± 9.81 times faster than java -version

It’s probably as fast as you can get.

Conclusion

Sometimes the fastest solution is to find the information you need in a different place. I will admit that the tool solves a problem that most people don’t have, but solving it helped improve the startup time of my tools.

I hope you still found the second part of my mini-series on java -version interesting, and I can assure you it was the last. The next blog post will be on reading and writing JFR files programmatically.

This article is part of my work in the SapMachine team at SAP, making profiling and debugging easier for everyone.

P.S.: Stay warm in the cold and let the flow guide you.

Author

  • Johannes Bechberger

    Johannes Bechberger is a JVM developer working on profilers and their underlying technology in the SapMachine team at SAP. This includes improvements to async-profiler and its ecosystem, a website to view the different JFR event types, and improvements to the FirefoxProfiler, making it usable in the Java world. He started at SAP in 2022 after two years of research studies at the KIT in the field of Java security analyses. His work today is comprised of many open-source contributions and his blog, where he writes regularly on in-depth profiling and debugging topics, and of working on his JEP Candidate 435 to add a new profiling API to the OpenJDK.

    View all posts

New posts like these come out at least every two weeks, to get notified about new posts, follow me on BlueSky, Twitter, Mastodon, or LinkedIn, or join the newsletter:

Leave a Reply

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