Java 16: An Introduction from a Java 8 Monogamist

With the recent release of Java 16, we thought it would be nice to provide a quick overview of its new features and upgrades. While the update introduces a number of behind-the-scenes changes, this post will focus instead on coding features that are more immediately relevant to programmers in their daily work. Before diving in, let's take a look at the coding upgrades made between Java 9 and Java 15.

Java 9 and 10

JShell: execute and experiment with Java code without creating a class or main method; those of you familiar with the Python shell will quickly be able to put this to good use

|  Welcome to JShell -- Version 9
|  For an introduction type: /help intro
jshell> "This is my long string. I want a part of it".substring(8,19);
$5 ==> "my long string"

.copyOf(): create an unmodifiable copy of lists, maps, and sets (modify throws exception)

List<Integer> copyList = List.copyOf(someIntList);
copyList.add(4); //throws exception

.toUnmodifable(): similar to copyOf(), but for stream collection

List<Integer> evenList =
  .filter(i -> i % 2 == 0)
evenList.add(4); //throws exception

.orElseThrow(): if no value is present for an optional, throw a specified exception

Integer firstEven =
  .filter(i -> i % 2 == 0)

var: new reserved type name, similar to its use in Scala; allows for type inference; must be initialized, and cannot be initialized to null (needed for compiler to determine what to do); only supported for local in-method variables

Map<Integer, String> map = new HashMap<>();
var idToNameMap = new HashMap<Integer, String>();

var message = "Hello, Java 10";
assertTrue(message instance of String);

Java 11

New helpful String methods: .isBlank() .lines() .strip() .stripLeading() .stripTrailing()

String multilineString = "I love \n \n Java 11 \n a lot.";
List<String> lines = multilineString.lines()
  .filter(line -> !line.isBlank())
assertThat(lines).containsExactly("I love", "Java 11", "a lot.");

.toArray(): simpler collection conversion to an array. Why is this useful? Arrays are of a fixed size, have better performance (although cost more memory), and can hold either objects or primitives

List sampleList = Arrays.asList("Java", "Kotlin");
String[] sampleArray = sampleList.toArray(String[]::new);

assertThat(sampleArray).containsExactly("Java", "Kotlin");

var now supported for lambda parameters: more readable than explicit type definition; allows for type annotations within streams

List<String> sampleList = Arrays.asList("Java", "Kotlin");
String resultString =
  .map((@Nonnull var x) -> x.toUpperCase())
  .collect(Collectors.joining(", "));
assertThat(resultString).isEqualTo("JAVA, KOTLIN");

Directly run java without javac compiling: allows for single file source code, without the need for pre-compilation

$ javac
$ java HelloWorld 
Hello Java 8!
$ java
Hello Java 11!

Java 12

New String methods: String .indent() and .transform()

String text = "Hello Ippon!\nThis is a Java 12 blog post.";

text = text.indent(4);
//prints this (with an indent of 4 spaces)
    Hello Ippon!
    This is a Java 12 blog post.
String text = "Hello Ippon!\nThis is a Java 12 blog post.";

text = text.indent(-10);
//prints this (with no indent)
Hello Ippon!
This is a Java 12 blog post.
String text = "Ippon";
String transformed = text.transform(value ->
      new StringBuilder(value).reverse().toString()

assertEquals("noppI", transformed);

Files package .mismatch() static method: will return -1 if two files are matching, or the index of the first differentiating byte; no more byte array comparisons!

Path filePath1 = Files.createTempFile("file1", ".txt");
Path filePath2 = Files.createTempFile("file2", ".txt");
Files.writeString(filePath1, "Java 12 Article");
Files.writeString(filePath2, "Java 12 Article");

long mismatch = Files.mismatch(filePath1, filePath2);
assertEquals(-1, mismatch);

CompactNumberFormat: convert numbers to strings based on patterns provided by a given locale

NumberFormat shortFormat = 
      NumberFormat.getCompactNumberInstance(new Locale("en", "US"), NumberFormat.Style.SHORT);
assertEquals("2.59K", shortFormat.format(2592));

NumberFormat longFormat = 
      NumberFormat.getCompactNumberInstance(new Locale("en", "US"), NumberFormat.Style.LONG);
assertEquals("2.59 thousand", longFormat.format(2592));

Java 13 and 14

Improved switch expression concision: we can now define multiple cases within a single line, without the need for breaks

boolean isTodayHoliday;
switch (day) {
    case "MONDAY":
    case "TUESDAY":
    case "WEDNESDAY":
    case "THURSDAY":
    case "FRIDAY":
        isTodayHoliday = false;
    case "SATURDAY":
    case "SUNDAY":
        isTodayHoliday = true;
        throw new IllegalArgumentException("What's a " + day);


boolean isTodayHoliday = switch (day) {
    case "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY" -> false;
    case "SATURDAY", "SUNDAY" -> true;
    default -> throw new IllegalArgumentException("What's a " + day);

More helpful and descriptive Null Pointer Exceptions. I can already hear you quietly saying to yourself, "Finally!"

int[] arr = null;
arr[0] = 1;
Exception in thread "main" java.lang.NullPointerException
at com.baeldung.MyClass.main(


int[] arr = null;
arr[0] = 1;
java.lang.NullPointerException: Cannot store to int array because "a" is null

Java 15

Text blocks: Now fully supported as a production ready feature

String html = """
              		  <p>Hello World.</p>
String multiline = "A quick brown fox jumps over a lazy dog; the lazy dog howls loudly.";
String multiline = """
    A quick brown fox jumps over a lazy dog; \
    the lazy dog howls loudly.""";

Sealed classes: More control over and ability to be restrictive with inheritance; the below code will prevent any object other than Employee and Manager from ever extending Person

public abstract sealed class Person permits Employee, Manager {

public final class Employee extends Person {}

public non-sealed class Manager extends Person {}

//the below will not throw a warning, even without 
//a final 'else' condition, as the compiler can infer 
//from the sealed class definition that all possibilities 
//have been exhausted

if (person instanceof Employee) {
    return ((Employee) person).getEmployeeId();
else if (person instanceof Manager) {
    return ((Manager) person).getSupervisorId();

Java 16

Records: More concise definitions of immutable DTOs; compiler will also auto-provide toString, equals, and hashCode methods. (Note: while records were previewed in Java versions 14 and 15, Java 16 releases them as a production-ready feature)

//Immutable DTO Before
public class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) { = name;
        this.age = age;

    public String getName() {
        return name;

    public int getAge() {
        return age;

//vs Immutable DTO After

public record Person(String name, int age) {}
public record Person(String name, int age) {
    public Person {
        if(age < 0) {
            throw new IllegalArgumentException("Age cannot be negative");

Pattern matching: Remove boilerplate code required for instanceof checks; you can now have pattern matching and conditionals within the same line of code. (Note: similar to Records, this was previewed in earlier versions, but is now a fully supported production-ready feature)

if (person instanceof Employee) {
    Employee employee = (Employee) person;
    Date hireDate = employee.getHireDate();


if (person instanceof Employee employee) {
    Date hireDate = employee.getHireDate();


if (person instanceof Employee employee && employee.getYearsOfService() > 5) {

You probably noticed that these updates don't appear to include all that many changes. This is both because Java releases now occur every 6 months, and that they tend to include mostly under-the-hood upgrades. For a more in-depth look at the differences between Java's LTS and non-LTS releases, see:

We hope that this introduction will allow for you to more quickly dive into post-Java 8 versions and use what they have to offer in your daily work. For more information regarding non-coding-related changes made in these updates, be sure to check out Oracle's very helpful JDK release notes library. Thanks for reading!


Ippon Blog
Journal Dev
Info World
Javat Point
Plural Sight

Author image
Mike Mitchell is a Backend Software Engineer for Ippon Technologies primarily focused in Java and the Spring framework.
Richmond, Va LinkedIn
Ippon Technologies is an international consulting firm that specializes in Agile Development, Big Data and DevOps / Cloud. Our 400+ highly skilled consultants are located in the US, France, Australia and Russia. Ippon technologies has a $42 million revenue.