The new six-month release cadence of the JDK means before we’ve even really figured out what the new features are in JDK 10, along comes JDK 11. I posted an earlier blog where I listed all 109 new features and APIs I could find in JDK 10, so it seemed obvious to do the same thing for JDK 11. I’m going to use a different format to the previous post. In this post, I’ll divide things two sections: features that are visible to developers and everything else. This way if you’re interested in just what will affect your development work you can skip the second part.

The total I counted was 90 (that’s JEPs plus new classes and methods, excluding the individual ones for the HTTP client and Flight Recorder). Although that’s eleven less than I found in JDK 10, I think it’s fair to say more functionality has been added to JDK 11, certainly at the JVM level.

Developer Visible Features

JDK 11 is pretty light on things that change the way you code. There is one small change to the language syntax, a fair number of new APIs and the ability to run single-file applications without the need to use the compiler. Also, visible is the removal of the java.se.ee aggregator module, which may impact migrating an existing application to JDK 11.

JDK 10 introduced Local-Variable Type Inference (JEP 286). This simplifies code, as you no longer need to explicitly state the type of a local-variable but can, instead, use var. JEP 323 extends the use of this syntax to the parameters of Lambda expressions. Here’s a simple example:

list.stream() .map((var s) -> s.toLowerCase()) .collect(Collectors.toList());

Of course, the astute Java programmer would point out that Lambda expressions already have type inference so the use of var would (in this case) be superfluous. We could just as easily write the same code as:

list.stream() .map(s -> s.toLowerCase()) .collect(Collectors.toList());

Why add var support, then? The answer is for one special case, which is when you want to add an annotation to the Lambda parameter. It is not possible to do this without a type being involved. To avoid having to use the explicit type we can use var to simplify things, thus:

list.stream() .map((@Notnull var s) -> s.toLowerCase()) .collect(Collectors.toList());

This feature has required changes to the Java Language Specification (JLS), specifically:

Page 24: The description of the var special identifier.

Page 627-30: Lambda parameters

Page 636: Runtime evaluation of Lambda expressions

Page 746: Lambda syntax

One of the criticisms of Java is that it can be verbose in its syntax and the ‘ceremony’ associated with running even a trivial application can make it hard to approach as a beginner. To write an application that just prints “Hello World!” requires you to write a class with a public static void main method and use the System.out.println method. Having done this, you must then compile the code using javac. Finally, you can run the application to be welcomed to the world. Doing the same thing in most scripting languages is significantly simpler and quicker.

JEP 330 eliminates the need to compile a single-file application, so now you can type

java HelloWorld.java

The java launcher will identify that the file contains Java source code and will compile the code to a class file before executing it.

Parameters placed after the name of the source file are passed as parameters when executing the application. Parameters placed before the name of the source file are passed as parameters to the java launcher after the code has been compiled (this allows for things like the classpath to be set on the command line). Parameters that are relevant to the compiler (such as the classpath) will also be passed to javac for compilation.

As an example:

java -classpath /home/foo/java Hello.java Bonjour

would be equivalent to:

javac -classpath /home/foo/java Hello.java java -classpath /home/foo/java Hello Bonjour

This JEP also provides ‘shebang’ support. To reduce the need to even mention the java launcher on the command line, this can be included on the first line of the source file. For example:

#!/usr/bin/java --source 11 public class HelloWorld { ...

It is necessary to specify the –source flag with the version of Java to use.

JDK 9 introduced a new API to provide support for the HTTP Client protocol (JEP 110). Since JDK 9 introduced the Java Platform Module System (JPMS), this API was included as an incubator module. Incubator modules are intended to provide new APIs but without making them part of the Java SE standard. Developers can try the API and provide feedback. Once any necessary changes have been made (this API was updated in JDK 10), the API can be moved to become part of the standard.

The HTTP Client API is now part of the Java SE 11 standard. This introduces a new module and package to the JDK, java.net.http. The main types defined are

HttpClient

HttpRequest

HttpResponse

WebSocket

The API can be used synchronously or asynchronously. Asynchronous mode makes use of CompletableFutures and CompletionStages.

With the introduction of JPMS in JDK 9, it was possible to divide the monolithic rt.jar file into multiple modules. An additional advantage of JPMS is it is now possible to create a Java runtime that only includes the modules you need for your application, reducing the size considerably. With cleanly defined module boundaries it is now simpler to remove parts of the Java API that are outdated. This is what this JEP does; the java.se.ee meta-module includes six modules that will no longer be part of the Java SE 11 standard and not included in the JDK. The affected modules are:

corba

transaction

activation

xml.bind

xml.ws

xml.ws.annotation

These modules have been deprecated since JDK 9 and were not included by default in either compilation or runtime. If you had tried compiling or running an application that used APIs from these modules on JDK 9 or JDK 10 they would have failed. If you use APIs from these modules in your code, you will need to supply them as a separate module or library. From asking audiences at my presentations, it seems that the java.xml modules, which are part of the JAX-WS, SOAP-based web services support are the ones that will cause most problems.

New APIs

A lot of the new APIs in JDK 11 result from the HTTP client module now being part of the standard, as well as the inclusion of Flight Recorder.

For a complete list of API changes, I refer the reader to the excellent comparison of different JDK versions produced by Gunnar Morling, which is available on Github.

What I list here are all the new methods other than those in the java.net.http and jdk.jfr modules. I’ve also not listed the new methods and classes in the java.security modules, which are pretty specific to the changes of JEP 324 and JEP 329 (there are six new classes and eight new methods).

java.io.ByteArrayOutputStream

void writeBytes(byte[]): Write all the bytes of the parameter to the output stream

java.io.FileReader

Two new constructors that allow a Charset to be specified.

java.io.FileWriter

Four new constructors that allow a Charset to be specified.

java.io.InputStream

io.InputStream nullInputStream(): Returns an InputStream that reads no bytes. When you first look at this method (and the ones in OutputStream, Reader and Writer), you wonder what use they are. You can think of them like /dev/null for throwing away output you don’t need or providing an input that always returns zero bytes.

java.io.OutputStream

io.OutputStream nullOutputStream()

java.io.Reader

io.Reader nullReader()

java.io.Writer

io.Writer nullWriter()

java.lang.Character

String toString(int): This is an overloaded form of the existing method but takes an int instead of a char. The int is a Unicode code point.

java.lang.CharSequence

int compare(CharSequence, CharSequence): Compares two CharSequence instances lexicographically. Returns a negative value, zero, or a positive value if the first sequence is lexicographically less than, equal to, or greater than the second, respectively.

java.lang.ref.Reference

lang.Object clone(): I must admit, this one confuses me. The Reference class does not implement the Cloneable interface and this method will always throw a CloneNotSupportedException. There must be a reason for its inclusion, presumably for something in the future.

java.lang.Runtime

java.lang.System

No new methods here but worth mentioning that the runFinalizersOnExit() method has now been removed from both these classes (this could be a compatibility issue)

java.lang.String

I think this is one of the highlights of the new APIs in JDK 11. There are several useful new methods here.

boolean isBlank(): Returns true if the string is empty or contains only white space codepoints, otherwise false.

Stream lines(): Returns a stream of lines extracted from this string, separated by line terminators.

String repeat(int): Returns a string whose value is the concatenation of this string repeated count times.

String strip(): Returns a string whose value is this string, with all leading and trailing whitespace removed.

String stripLeading(): Returns a string whose value is this string, with all leading whitespace removed.

String stripTrailing(): Returns a string whose value is this string, with all trailing whitespace removed.

You probably look at strip() and ask, “How is this different to the existing trim() method?” The answer is that how whitespace is defined differs between the two.

java.lang.StringBuffer

java.lang.StringBuilder

Both these classes have a new compareTo() method that takes a StringBuffer/StringBuilder and returns an int. The lexographical comparison method is the same as the new compareTo() method of CharSequence.

java.lang.Thread

No additional methods but the destroy() and stop(Throwable) methods have been removed. The stop() method that takes no arguments is still present. This might present a compatibility issue.

java.nio.ByteBuffer

java.nio.CharBuffer

java.nio.DoubleBuffer

java.nio.FloatBuffer

java.nio.LongBuffer

java.nio.ShortBuffer

All these classes now have a mismatch() method that finds and returns the relative index of the first mismatch between this buffer and a given buffer.

java.nio.channels.SelectionKey

int interestOpsAnd(int): Atomically sets this key’s interest set to the bitwise intersection (“and”) of the existing interest set and the given value.

int interestOpsOr(int): Atomically sets this key’s interest set to the bitwise union (“or”) of the existing interest set and the given value.

java.nio.channels.Selector

int select(java.util.function.Consumer, long): Selects and performs an action on the keys whose corresponding channels are ready for I/O operations. The long parameter is a timeout.

int select(java.util.function.Consumer): As above, except without the timeout.

int selectNow(java.util.function.Consumer): As above, except it is non-blocking.

java.nio.file.Files

String readString(Path): Reads all content from a file into a string, decoding from bytes to characters using the UTF-8 charset.

String readString(Path, Charset): As above, except decoding from bytes to characters using the specified Charset.

Path writeString(Path, CharSequence, java.nio.file. OpenOption[]):Write a CharSequence to a file. Characters are encoded into bytes using the UTF-8 charset.

Path writeString(Path, CharSequence, java.nio.file. Charset, OpenOption[]): As above, except Characters are encoded into bytes using the specified Charset.

java.nio.file.Path

Path of(String, String[]): Returns a Path by converting a path string, or a sequence of strings that when joined form a path string.

Path of(net.URI): Returns a Path by converting a URI.

java.util.Collection

Object[] toArray(java.util.function.IntFunction): Returns an array containing all of the elements in this collection, using the provided generator function to allocate the returned array.

java.util.concurrent.PriorityBlockingQueue

java.util.PriorityQueue

void forEach(java.util.function.Consumer): Performs the given action for each element of the Iterable until all elements have been processed or the action throws an exception.

boolean removeAll(java.util.Collection): Removes all of this collection’s elements that are also contained in the specified collection (optional operation).

boolean removeIf(java.util.function.Predicate): Removes all of the elements of this collection that satisfy the given predicate.

boolean retainAll(java.util.Collection): Retains only the elements in this collection that are contained in the specified collection (optional operation).

java.util.concurrent.TimeUnit

long convert(java.time.Duration): Converts the given time duration to this unit.

java.util.function.Predicate

Predicate not(Predicate). Returns a predicate that is the negation of the supplied predicate.

This is one of my favourite new APIs in JDK 11. As an example, you can convert this code:

lines.stream() .filter(s -> !s.isBlank())

to

lines.stream() .filter(Predicate.not(String::isBlank))

and if we use a static import it becomes:

lines.stream() .filter(not(String::isBlank))

Personally, I think this version is more readable and easier to understand.

java.util.Optional

java.util.OptionalInt

java.util.OptionalDouble

java.util.OptionalLong

boolean isEmpty():If a value is not present, returns true, otherwise false.

java.util.regex.Pattern

Predicate asMatchPredicate(): I think this could be a hidden gem in the new JDK 11 APIs. Creates a predicate that tests if this pattern matches a given input string.

java.util.zip.Deflater

int deflate(ByteBuffer): Compresses the input data and fills the specified buffer with compressed data.

int deflate(ByteBuffer, int): Compresses the input data and fills the specified buffer with compressed data. Returns the actual number of bytes of data compressed.

void setDictionary(ByteBuffer): Sets the preset dictionary for compression to the bytes in the given buffer. This is an overloaded form of an existing method that can now accept a ByteBuffer, rather than a byte array.

void setInput(ByteBuffer): Sets input data for compression. Also an overloaded form of an existing method.

java.util.zip.Inflater

int inflate(ByteBuffer): Uncompresses bytes into the specified buffer. Returns the actual number of bytes uncompressed.

void setDictionary(ByteBuffer): Sets the preset dictionary to the bytes in the given buffer. An overloaded form of an existing method.

void setInput(ByteBuffer): Sets input data for decompression. An overloaded form of an existing method.

javax.print.attribute.standard.DialogOwner

This is a new class in JDK 11 and is an attribute class used to support requesting a print or page setup dialog be kept displayed on top of all windows or some specific window.

javax.swing.DefaultComboBoxModel

javax.swing.DefaultListModel

void addAll(Collection): Adds all of the elements present in the collection.

void addAll(int, Collection): Adds all of the elements present in the collection, starting from the specified index.

javax.swing.ListSelectionModel

int[] getSelectedIndices(): Returns an array of all of the selected indices in the selection model, in increasing order.

int getSelectedItemsCount(): Returns the number of selected items.

jdk.jshell.EvalException

jshell.JShellException getCause(): Returns the wrapped cause of the throwable in the executing client represented by this EvalException or null if the cause is non-existent or unknown.

Non-Developer Features

Java (and other languages) supports nested classes through inner classes. To make this work correctly requires the compiler to perform some tricks. Here’s an example:

public class Outer { private int outerInt; class Inner { public void printOuterInt() { System.out.println("Outer int = " + outerInt); } } }

The compiler modifies this to create something like this before performing compilation:

public class Outer { private int outerInt; public int access$000() { return outerInt; } } class Inner$Outer { Outer outer; public void printOuterInt() { System.out.println("Outer int = " + outer.access$000()); } }

Although, logically, the inner class is part of the same code entity as the outer class it is compiled as a separate class. It, therefore, requires a synthetic bridge method to be created by the compiler to provide access to the private field of the outer class.

This JEP introduces the concept of nests, where two members of the same nest (Outer and Inner from our example) are nestmates. Two new attributes are defined for the class file format, NestHost and NestMembers. These changes are useful for other languages that support nested classes and are compiled to bytecodes.

This feature introduces three new methods to java.lang.Class:

Class getNestHost()

Class[] getNestMembers()

boolean isNestmateOf(Class)

This feature also required changes to the Java Virtual Machine Specification (JVMS), specifically in section 5.4.4, Access Control.

This JEP describes an extension to the class-file format to support a new constant-pool form, CONSTANT_Dynamic (often referred to in presentations as condy). The idea of a dynamic constant seems to be an oxymoron but, essentially, you can think of it like a final value in Java. The constant-pool value is not set at compile-time (unlike the other constants) but uses a bootstrap method to determine the value at runtime. The value is therefore dynamic but, since its value is only set once, it is also constant.

This feature is primarily aimed at people developing new languages and compilers that will generate bytecodes and class files as output to be run on the JVM. It simplifies some of the tasks required for this.

This feature introduces a new class, java.lang.invoke.ConstantBootstraps, with nine new methods. I won’t list them all here; these are the bootstrap methods for dynamically computed constants.

This feature required changes to the JVMS, specifically in the areas of how the invokespecial bytecode is used and section 4.4, The Constant Pool.

This was a JEP contributed by Red Hat. The JVM is now able to take advantage of more of the specialised instructions available in the Arm 64 instruction set. Specifically, this improves performance of the sin(), cos() and log() methods of the java.lang.Math class.

Red Hat also contributed this JEP. The Epsilon collector is somewhat unusual, in that it does not collect any garbage! It will allocate memory, as required, when new objects are instantiated but does not reclaim any space occupied by unreferenced objects.

When you first look at this, you think what is the point of that? It turns out that there are two uses:

Primarily, this collector is designed to enable new GC algorithms to be evaluated in terms of their performance impact. The idea is to run a sample application with the Epsilon GC and generate a set of metrics. The new GC algorithm is turned on, the same tests run and the metric results compared. For very short-lived tasks (think serverless functions in the cloud) where you can guarantee that you will not exceed the memory allocated to the heap. This can improve performance by not having any overhead (including gathering statistics necessary to decide when to run the collector) on the application code.

If the heap space is exhausted, the JVM can be configured to fail in one of three ways:

A conventional OutOfMemoryError is thrown. Perform a heap dump Fail the JVM hard and optionally perform another task (like running a debugger).

Cryptographic standards are continually changing and improving. In this case, the existing elliptic-curve Diffie-Hellman scheme is being replaced by Curve25519 and Curve448. This is the key agreement scheme defined by RFC-7748.

The Java platform supports Unicode to enable all character sets to be processed. Since Unicode has been updated to version 10, the JDK has also been updated to support this revision to the standard.

I’m always intrigued to see what the Unicode maintainers find to include in new versions. Unicode 10 has 8,518 new symbols. This includes the Bitcoin symbol, the Nüshu character set (used by Chinese women to write poetry) as well as Soyombo and Zanabazar Square (which are characters used in historic Buddhist texts to write Sanskrit, Tibetan, and Mongolian). There are also lots more Emojis, including the long-awaited (apparently) Colbert Emoji.

Remember that since JDK 9, you can use UTF-8 in property files. This means any Unicode character can be used in a property file. Including Emojis. Or Nüshu.

Flight Recorder is a low-overhead data collection framework for the JVM. Prior to JDK 11, this was a commercial feature in the Oracle JDK binary. Now that Oracle is eliminating functional differences between the Oracle JDK and one built from OpenJDK source code, this feature has been contributed to the OpenJDK.

There are four parts to this:

Provide APIs for producing and consuming data as events

Provide a buffer mechanism and a binary data format

Allow the configuration and filtering of events

Provide events for the OS, the HotSpot JVM, and the JDK libraries

There are two new modules for this: jdk.jfr and jdk.management.jfr.

Similar to JEP 324, this is an updating of ciphers used by the JDK. In this case, implement the ChaCha20 and ChaCha20-Poly1305 ciphers as specified in RFC 7539. ChaCha20 is a relatively new stream cipher that can replace the older, insecure RC4 stream cipher.

Somewhat surprisingly, this is a JEP contributed by Google. This provides a way to get information about Java object heap allocations from the JVM that:

Is low-overhead enough to be enabled by default continuously

Is accessible via a well-defined, programmatic interface

Can sample all allocations

Can be defined in an implementation-independent way (i.e., not limited to a particular GC algorithm or VM implementation)

Can give information about both live and dead Java objects.

TLS 1.3 (RFC 8446) is a major overhaul of the TLS protocol and provides significant security and performance improvements over previous versions. The JDK now supports this, although this does not extend to Datagram Transport Layer Security (DTLS).

This is a new, experimental garbage collector designed for use with applications that require a large (multi-gigabyte) heap and low-latency. It uses a single generation heap (which is a bit unusual, given the accepted wisdom of the Weak Generational Hypothesis) and performs most (but not all) of the GC work concurrently with the application. It does this through the use of a read-barrier that intercepts each read to an object from the application and ensures that the reference returned is correct. This eliminates the issue of being able to relocate objects concurrently while application threads are running.

ZGC is region-based (like G1), NUMA aware and compacting. It is not intended as a general-purpose collector.

If you want a genuinely pauseless collector with low-latency, I can heartily recommend C4 in our Zing JVM.

Nashorn was introduced in JDK 8 as a higher-performing replacement to the Rhino Javascript engine. The intention is to remove Nashorn, along with the associated APIs and jjs tool from a future version of Java. When this happens has not been decided yet. The possibility of using the Graal VM as a replacement has been suggested but how that will work has not been evaluated.

Pack200 is a compression scheme for JAR files, introduced in Java SE 5.0. With the introduction of the JPMS in JDK 9, Pack200 is no longer used to compress the JDK itself. The pack200 and unpack200 tools, and the Pack200 API in java.util.jar are now deprecated and may be removed in a future version of the JDK. When this happens has not been specified.

Conclusions

JDK 11 is the next LTS release of the JDK (as defined by Oracle and being followed by everyone else). Although there are not a lot of developer-focused features, there’s a lot going on lower down in the JVM laying the groundwork for future more prominent features.

Our Zulu builds of JDK 11 can be found here and are completely free!

Is it time to start migrating your applications to JDK 11?