Java 10 Time-Based Release Versioning and Runtime.Version

Introduction Java 10 was the first release under Java's six-month release cadence, and with it Java adopted a new, time-based way of numbering its versions (JEP 322). This tutorial explains time-based release versioning in Java. We will first see why the release model changed and how long-term support (LTS) releases fit into it. We will then go through each part of a version number such as 11.0.21+9. Lastly, we will read and compare versions in code with the Runtime.Version API, which got new methods in Java 10. From feature-driven to time-driven releases Until Java 9, a major Java release shipped when its big features were ready. This made release dates unpredictable - Java 9, for example, was delayed several timesClick to read: delayed several times waiting for the module system, and came more than three years after Java 8. Finished smaller features had to wait for the big ones.

From Java 10, a new feature release ships every six months, in March and September, with whatever features are ready by then. A feature which is not ready simply goes into a later release. Java 10 came out in March 2018, Java 11 in September 2018, and so on.

Each feature release gets updates only until the next one comes out. To give organisations a version they can stay on for years, some releases are designated long-term support (LTS) releases, which vendors keep updating for several years. Java 11 was the first LTS release under the new model, followed by Java 17, 21 and 25 - since Java 17, an LTS release comes every two years.
The version number format Java versions now follow the format -
$FEATURE.$INTERIM.$UPDATE.$PATCH
  • $FEATURE - the feature release, incremented every six months: 10, 11, 12 and so on. It is the number people mean when they say "Java 11".
  • $INTERIM - for a release between feature releases with compatible fixes. The six-month model has no need for such releases, so it is always 0.
  • $UPDATE - incremented for each update release with security and bug fixes, released every quarter in January, April, July and October. For example, 11.0.21 is the 21st update of Java 11.
  • $PATCH - for an emergency patch release outside the quarterly schedule. It is rarely used.
Trailing zeros are left out, so the first release of Java 11 is just 11, not 11.0.0.0. A full version string can also have a build number after a +, and optional information after a -, as in 21.0.5+11-LTS. This replaced the older scheme, in which Java 8 updates were numbered like 1.8.0_202, and Java 9 used $MAJOR.$MINOR.$SECURITY, as in 9.0.4. Reading the version with Runtime.Version Java 9 added the Runtime.Version class, which represents a version string and can be parsed and compared. Java 10 added the methods feature(), interim(), update() and patch(), matching the new format, and deprecated Java 9's major(), minor() and security(). Runtime.version() returns the version of the running JVM -
Reading the running Java version
//VersionDemo.java
public class VersionDemo {
  public static void main(String[] args) {
    Runtime.Version version = Runtime.version();
    System.out.println("Version : " + version);
    System.out.println("feature : " + version.feature());
    System.out.println("interim : " + version.interim());
    System.out.println("update  : " + version.update());
    System.out.println("patch   : " + version.patch());
    System.out.println("build   : " + version.build().orElse(-1));

    System.out.println("java.version property: " + System.getProperty("java.version"));
    if (version.feature() >= 11) {
      System.out.println("Running on Java 11 or later - the HTTP Client is available");
    }
  }
}
 OUTPUT of the above code
Version : 24.0.1+9-30
feature : 24
interim : 0
update  : 1
patch   : 0
build   : 9
java.version property: 24.0.1
Running on Java 11 or later - the HTTP Client is available
Explanation of the code
  • This output is from JDK 24.0.1, build 9. Run the program on your own JDK and the numbers will match your version.
  • version.feature() is the simplest way to check for a minimum Java version. Before Java 9, code had to parse the java.version system property, which was error-prone because of the 1.8.0_202 style format.
  • build() returns an Optional<Integer>, because a version string does not always have a build number.
Parsing and comparing versions Runtime.Version.parse() parses any version string, and versions can be compared with compareTo(), which compares them number by number rather than as text -
Parsing and comparing version strings
//VersionCompareDemo.java
import java.util.List;
import java.util.stream.Collectors;

public class VersionCompareDemo {
  public static void main(String[] args) {
    List<String> strings = List.of("11.0.21+9", "10", "17.0.2", "9.0.4", "11.0.3", "21.0.5+11-LTS");
    for (String s : strings) {
      Runtime.Version v = Runtime.Version.parse(s);
      System.out.printf("%-14s feature=%-3d update=%-3d build=%s%n",
          s, v.feature(), v.update(), v.build().map(String::valueOf).orElse("-"));
    }

    List<String> sorted = strings.stream()
        .map(Runtime.Version::parse)
        .sorted()
        .map(Runtime.Version::toString)
        .collect(Collectors.toList());
    System.out.println("Sorted: " + sorted);

    // 11.0.21 is newer than 11.0.3, although "11.0.21" < "11.0.3" as text
    System.out.println("Text comparison says 11.0.21 is newer: " + ("11.0.21".compareTo("11.0.3") > 0));
    System.out.println("Version comparison says 11.0.21 is newer: "
        + (Runtime.Version.parse("11.0.21").compareTo(Runtime.Version.parse("11.0.3")) > 0));
  }
}
 OUTPUT of the above code
11.0.21+9      feature=11  update=21  build=9
10             feature=10  update=0   build=-
17.0.2         feature=17  update=2   build=-
9.0.4          feature=9   update=4   build=-
11.0.3         feature=11  update=3   build=-
21.0.5+11-LTS  feature=21  update=5   build=11
Sorted: [9.0.4, 10, 11.0.3, 11.0.21+9, 17.0.2, 21.0.5+11-LTS]
Text comparison says 11.0.21 is newer: false
Version comparison says 11.0.21 is newer: true
Explanation of the code
  • The old Java 9 version 9.0.4 is parsed with the same rules, so its third number appears as the update number.
  • Runtime.Version implements Comparable, so a list of versions sorts in the correct order. Comparing the strings as text would put 11.0.21 before 11.0.3, because the character '2' comes before '3'.
  • compareTo() also takes the build number and optional information into account. compareToIgnoreOptional() ignores the optional part after the -.
Summary In this tutorial we saw how Java 10 moved Java to a new feature release every six months, and how long-term support releases such as Java 11, 17, 21 and 25 give organisations versions they can stay on for years. We went through the time-based $FEATURE.$INTERIM.$UPDATE.$PATCH version format. We then read the running Java version with Runtime.version() and the feature(), interim(), update() and patch() methods added in Java 10, and parsed and compared version strings. In the next tutorial, we will see how to generate JNI headers with javac -hClick to read tutorial: Java 10 Removed javah: Generate JNI Headers with javac -h, now that Java 10 has removed the javah tool.