1. Language Changes for Java SE 9

1.1. Platform module system

The major change to Java 9 is the introduction of the Java Platform module system.

The Java Platform module system introduces a new kind of Java programming component — the module, which is a named, self-describing collection of code and data. Its code is organized as a set of packages containing types (i.e., Java classes and interfaces). Its data includes resources and other kinds of static information. Modules can either export or encapsulate packages, and they express dependencies on other modules explicitly.

To learn more about the Java Platform module system, see Project Jigsaw on OpenJDK.

1.2. Collections .of() factory methods

Java 9 introduces collection literals for the easier definition of the common collections:

List list = List.of(1, 2, 3);
Set set = Set.of("foo", "bar", "baz");
Map map = Map.of("hello", "world");
Map mapOfEntries = Map.ofEntries(Map.entry("o", 0), Map.entry("z", 1));

1.3. Stream API changes

1.3.1. takeWhile

takeWhile takes elements from the initial stream while the predicate holds true. Meaning that when an element is encountered that does not match the predicate, the rest of the stream is discarded.

Stream.of(2, 4, 6, 8, 9, 10, 12)
      .takeWhile(n -> n % 2 == 0)
      .forEach(System.out::print); // 2468

1.3.2. dropWhile

dropWhile is essentially the opposite of takeWhile. Instead of taking elements from the stream until the first element which does not match the predicate, dropWhile drops these elements and includes the remaining elements in the returned stream.

Stream.of(2, 4, 6, 8, 9, 10, 12)
      .dropWhile(n -> n % 2 == 0)
      .forEach(System.out::print); // 91012

1.4. Optional API changes

1.4.1. or() method

The or() method gives you a fluent way of chaining behavior on Optional without checking if the value is present or not.

Optional.empty().or(() -> Optional.of("Hello world"));

1.4.2. Converting an Optional into a Stream

Now it’s possible to convert an Optional into a Stream containing at most one element. It’s really useful if you want to use the laziness of the Streams API. Namely, calling map() on the Optional executes the mapping function immediately, on the Stream — not.

Optional optional = Optional.of(1).map(x -> x * 3); // variable contains Optional[3]
Stream stream = Optional.of(1).stream().map(x -> x * 3); // variable contains lazy stream, that is not evaluated until terminal operation

1.4.3. ifPresentOrElse() method

In Java 8 you could specify the behavior you want to execute if the value in an Optional is present.

In Java 9 you can pass 2 Runnables to specify what to do if the value is present and otherwise.

Optional.empty().ifPresentOrElse(x -> System.out.println(x), () -> System.out.println("empty")); // empty

1.5. Process Management API

Java 9 adds the ProcessHandle class, which offers a rich API to inspect the processes.

ProcessHandle current = ProcessHandle.current(); current.pid() // prints current process id

1.6. StackWalker

StackWalker enables you to walk, filter and otherwise access stack traces in a very efficient manner

StackWalker.getInstance()
           .walk(s -> s.limit(5)
           .collect(Collectors.toList()));

1.7. More concise try-with-resources statements

If you already have a resource as a final or effectively final variable, you can use that variable in a try-with-resources statement without declaring a new variable. An "effectively final" variable is one whose value is never changed after it is initialized.

In Java SE 7 or 8, you would declare new variables, like this:

try (Resource r1 = resource1; Resource r2 = resource2) {
    // do something
}

In Java SE 9 and beyond, you don’t need to declare r1 and r2 in a try-with-resources statement:

try (resource1; resource2) {
    // do something
}

1.8. Private interface methods

Private interface methods are supported. This support allows non-abstract methods of an interface to share code between them.

1.9. The underscore character is not a legal name

If you use the underscore character ("_") an identifier, your source code can no longer be compiled.

2. Language Changes for Java SE 10

Java SE 10 introduces support for inferring the type of local variables from the context, which makes code more readable and reduces the amount of required boilerplate code.

2.1. Local-Variable Type Inference with var

In Java SE 10 and later, you can declare local variables with non-null initializers with the var identifier, which can help you write code that’s easier to read.

URL url = new URL("http://www.oracle.com/");
URLConnection conn = url.openConnection();
Reader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
var url = new URL("www.oracle.com");
var conn = url.openConnection();
var reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));

var is a reserved type name, not a keyword, which means that existing code that uses var as a variable, method,or package name is not affected. However, code that uses var as a class or interface name is affected and the class or interface needs to be renamed.

var can be used for the following types of variables:

  • Local variable declarations with initializers:

    var list = new ArrayList(); // infers ArrayList
    var stream = list.stream(); // infers Stream
    var path = Paths.get(fileName); // infers Path
    var bytes = Files.readAllBytes(path); // infers bytes[]
  • Enhanced for-loop indexes:

    List myList = Arrays.asList("a", "b", "c");
    for (var element : myList) { ... } // infers String
  • Index variables declared in traditional for-loop:

    for (var counter = 0; counter < 10; counter++) {...} // infers int
  • try-with-resources variable:

    try (var input = new FileInputStream("validation.txt")) {...} // infers FileInputStream
  • A lambda expression whose formal parameters have inferred types is implicitly typed:

    BiFunction = (a, b) -> a + b

var style guide: var should be used with caution. To get more details about recommended uses,consult with official style guide.

3. Language Changes for Java SE 11

3.1. Implicitly typed lambda expression

In Java SE 11 and later, you can declare each formal parameter of an implicitly typed lambda expression with the var identifier:

(var a, var b) -> a + b;

Why would we want to use var for lambda parameters when we could simply skip the types?

One benefit of uniformity is that annotations can be applied to lambda parameters:

(@Nonnull var s1, @Nullable var s2) -> s1 + s2

4. Java and Docker

One of the key features of Docker is the ability to limit a container’s memory and CPU usage. Unfortunately, this is precisely where Java runs short. Let’s use an example to understand the problem. Imagine you have a node with 32GB of memory and you want to run a Java application with a limit of 1GB. If you do not provide a -Xmx parameter the JVM will use its default configuration:

  1. The JVM will check the total available memory. Because the JVM is not aware of the Linux container, it thinks it is running on the Host machine and has access to the full 32GB of available memory.

  2. By default, the JVM will use MaxMemory/4 which in this case is 8GB (32GB/4).

  3. As the heap size grows and goes beyond 1GB, the container will be killed by Docker ("OOM killed").

  4. Of course, an obvious solution is to fix the JVM’s heap size using -Xmx parameter, but that means you need to control memory twice, once in Docker and once in the JVM.

The first workaround for this issue was released with Java 8u131 and Java 9:

  1. Use -XX:+UnlockExperimentalVMOptions

  2. Use -XX:+UseCGroupMemoryLimitForHeap which would tell the JVM to check for the cgroup memory limit to set the maximum heap size

  3. Use -XX:MaxRAMFraction, to limit portion of memory that can be allocated to the JVM.

  4. Finally, application should control explicitly the thread pools sizes, and limit common ForkJoinPool parallelism with -Djava.util.concurrent.ForkJoinPool.common.parallelism=2

So, with Java 8u131+ and Java 9 you’d have something like:

-XX:+UnlockExperimentalVMOptions
-XX:+UseCGroupMemoryLimitForHeap
-XX:MaxRAMFraction=2
-Djava.util.concurrent.ForkJoinPool.common.parallelism=2

Starting from Java 10 applying CPU and memory limits to containerized JVMs becomes straightforward. The JVM will detect hardware capability of the container correctly, tune itself appropriately and make a good representation of the available capacity to the application. As a result, not only CPU Sets but also CPU Shares are now examined by JVM. Furthermore, this becomes the default behaviour, and can only be disabled via -XX:-UseContainerSupport option.

Oleksii Zghurskyi