Java 15: JEP's that came along…

Avatar of Rakesh Mothukuri

Rakesh Mothukuri

| Reading Time : 13 minutes

Avatar of Rakesh Mothukuri Avatar of Rakesh Mothukuri Avatar of Rakesh Mothukuri Avatar of Rakesh Mothukuri

Introduction

Java 15 which came into General availability brought a very good set of 14 features. Out of those 14 features, few are addition to and few are removed/disabled from current java eco-system. Here are the list of features added

  • Edwards-Curve Digital Signature Algorithm (EdDSA)
  • Sealed Classes (Preview)
  • Hidden Classes
  • Remove the Nashorn JavaScript Engine
  • Reimplement the Legacy DatagramSocket API
  • Disable and Deprecate Biased Locking
  • Pattern Matching for instanceof (Second Preview)
  • ZGC: A Scalable Low-Latency Garbage Collector
  • Text Blocks
  • Shenandoah: A Low-Pause-Time Garbage Collector
  • Remove the Solaris and SPARC Ports
  • Foreign-Memory Access API (Second Incubator)
  • Records (Second Preview)
  • Deprecate RMI Activation for Removal

In this blog we will go through a little bit about what these new features are, how they can be used using few code snippets.

Edwards-Curve Digital Signature Algorithm (EdDSA)

One of the reason for including this feature in Java 15 is

  • Its a popular signature algorithm due to its security and performance compared to similar signatures ECDSA
  • caching up with popular crypto libraries such as OpenSSL and BoringSSL which already supports EdDSA.
  • Its only signature at the moment that is allowed in TLS 1.3

Code snippet To generate key pair and sign the same

KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519");
KeyPair kp = kpg.generateKeyPair();
// algorithm is pure Ed25519
Signature sig = Signature.getInstance("Ed25519");
sig.initSign(kp.getPrivate());
sig.update(msg);
byte[] s = sig.sign();

Code snippet Snippet to construct the public key

KeyFactory kf = KeyFactory.getInstance("EdDSA");
boolean xOdd = ...
BigInteger y = ...
NamedParameterSpec paramSpec = new NamedParameterSpec("Ed25519");
EdECPublicKeySpec pubSpec = new EdECPublicKeySpec(paramSpec, new EdPoint(xOdd, y));
PublicKey pubKey = kf.generatePublic(pubSpec);

More reading material : https://openjdk.java.net/jeps/339

Sealed Classes (Preview)

Primary objective of Sealed classes is to create a way for Java to support algebraic data types .

When we look at Java class hierarchy which enables re use of code by inheritance but Author of Sealed Classes Brian Goetz says however its not always for the purpose of code re-use but also to model various possibilities that exists in a domain.

For example if author of a class called Shape may intend that only particular classes can extend the shape but not every class thereby author of Shape class can write code to known subclasses of the Shape and do not require to write code to defend for unknown subclasses of Shape

“A sealed class or interface can be extended or implemented only by those classes and interfaces permitted to do so” -https://openjdk.java.net/jeps/360

Example:

package com.example.geometry;

public abstract sealed class Shape
    permits Circle, Rectangle, Square {...}

In the above code snippet class Shape is marked as sealed class by applying the modifier sealed to its declaration and permits clause specifies the classes allowed to extend the sealed class. Here only Circle, Rectange and Square can extend the Shape class.

Sealed class + JEP 375 Pattern matching

Main purpose of sealing a class is to let client code reason about all permitted subclasses. Traditionally way to reason about subclasses is with an if-else chain of instanceof Example :

int getCenter(Shape shape) {
    if (shape instanceof Circle) {
        return ... ((Circle)shape).center() ...
    } else if (shape instanceof Rectangle) {
        return ... ((Rectangle)shape).length() ...
    } else if (shape instanceof Square) {
        return ... ((Square)shape).side() ...
    }
}

Using the permits clause and letting compiler know allowed classes can extend a sealed class along with pattern matching, Instead of inspecting an instance of a sealed class with if-else, client code will be able to use switch over using the JEP 375 test patterns. This allows compiler to check that the patterns are exhaustive

Example:

int getCenter(Shape shape) {
    return switch (shape) {
        case Circle c    -> ... c.center() ...
        case Rectangle r -> ... r.length() ...
        case Square s    -> ... s.side() ...
    };
}

More reading material :https://openjdk.java.net/jeps/360

Hidden Classes

Hidden classes are for JVM and not aimed to change java language in any way. Hidden classes intention mainly aimed for the frameworks that generates classes at run time and use them via Reflection.

A hidden class can be unloaded when it is no longer reachable, or it can share the lifetime of a class loader so that it is unloaded only when the class loader is garbage collected.

Example code for creating a hidden class

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Base64;

public class Test {
    static final String testString = "ABC";
    public static void main(String[] args) throws Throwable {
        byte[] testStringInBytes = Base64.getDecoder().decode(testString);

        Class<?> proxy = MethodHandles.lookup()
                .defineHiddenClass(testStringInBytes,
                        true, MethodHandles.Lookup.ClassOption.NESTMATE)
                .lookupClass();
        System.out.println(proxy.getName());
    }
}

Important difference in how a hidden class is created is the name it gets on creation. A hidden class is not anonymous. Name of the class can be fetched via Class::getName and name has a sufficiently unusual form that it effectively makes the class invisible to all other classes. The name is the concatenation of:

  1. The binary name in internal form (JVMS 4.2.1) specified by this_class in the ClassFile structure, say A/B/C;
  2. The '.' character; and
  3. An unqualified name (JVMS 4.2.2) that is chosen by the JVM implementation.

For example it could look like com.rockey.Test/0x0000000800b94440

More reading material: https://openjdk.java.net/jeps/371

Reimplement the Legacy DatagramSocket API

This feature reimplements java.net.DatagramSocket and java.net.MulticastSocket APIs as their implementation dates back to JDK 1.0 which has a mix of both Java and C that was difficult to maintain for the authors. Authors idea is these new implementation will adapt well to work with virtual threads ( this is explored as Project loom). These reimplementation are enabled by default providing non-interruptible behaviour for datagram and multicast sockets by directly using the platform-default implementation of the selector provider (sun.nio.ch.SelectorProviderImpl and sun.nio.ch.DatagramChannelImpl)

More reading material:https://openjdk.java.net/jeps/373

Pattern Matching for instanceof (Second Preview)

With this JEP Java might start to embrace pattern matching like Many languages, from Haskell to C#, have embraced pattern matching for its brevity and safety. Pattern matching allows the desired ‘shape’ of an object to be expressed concisely (the pattern)

This is an enhancement in the Java programming language with pattern matching for the instanceof operator. Pattern matching allows common logic in a program, namely the conditional extraction of components from objects, to be expressed more concisely and safely.

Main driving factor behind this Nearly every program has a sort of logic that combines testing if an expression has a certain type or structure, and then conditionally extracting components of its state for further processing.

Example: instanceof-and-cast idiom (without using pattern)

if (obj instanceof String) {
    String s = (String) obj;
    // use s
}

Three things happening here so we can use the string value:

  • a test (is obj a String?)

  • a conversion (casting obj to String) and

  • the declaration of a new local variable (s)

It is tedious; doing both the type test and cast should be unnecessary. But most importantly, the repetition provides opportunities for errors to creep unnoticed into programs. To simplify above code using patterns first we need to see what a pattern is “A pattern is a combination of (1) a predicate that can be applied to a target, and (2) a set of binding variables that are extracted from the target only if the predicate successfully applies to it.”

Above code now can be written as

Example: simplifying the above code (Scope of s is restricted to if block)

if (obj instanceof String s) {
    // can use s here
} else {
    // can't use s here
}

if obj is an instance of String, then it is cast to String and assigned to the binding variable s. The binding variable is in scope in the true block of the if statement, and not in the false block of the if statement but this scope of the variable is dependent on the by the semantics of the containing expressions and statements. For example, in this code below will extend scope of s to else block as well depending on the condition evaluation

Example: (Scope of s is extended to else block)

if (!(obj instanceof String s)) {
    .. s.contains(..) ..
} else {
    .. s.contains(..) ..
}

Additional reading material: https://openjdk.java.net/jeps/375

ZGC: A Scalable Low-Latency Garbage Collector

This JEP essentially changes the Z Garabage collector from an experimental feature to a product feature. With the positive feedback received from community on this JEP when introduced in Java 11 JDK authors added a number of features and enhancements. Which are

  • Concurrent class unloading
  • Uncommitting unused memory (JEP 351)
  • Maximum heap size increased from 4TB to 16TB
  • Minimum heap size decreased to 8MB
  • -XX:SoftMaxHeapSize
  • Support for the JFR leak profiler
  • Support for class-data sharing
  • Limited and discontiguous address spaces
  • Support for placing the heap on NVRAM
  • Improved NUMA awareness
  • Multi-threaded heap pre-touching

Furthermore, all commonly used platforms are now supported:

ZGC is enabled by setting

-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

Text Blocks

This JEP will allow to add text blocks to the Java language. A text block is a multi-line string literal that avoids the necessity of escape sequences and automatically formats the string in a predictable way. Not only that but also addition of new methods to string to support text blocks

  • String::stripIndent(): used to strip away incidental white space from the text block content
  • String::translateEscapes(): used to translate escape sequences
  • String::formatted(Object... args): simplify value substitution in the text block

HTML example

Using “one-dimensional” string literals

String html = "<html>\n" +
              "    <body>\n" +
              "        <p>Hello, world</p>\n" +
              "    </body>\n" +
              "</html>\n";

Using a “two-dimensional” block of text

String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
              """;

SQL example

Using “one-dimensional” string literals

String query = "SELECT \"EMP_ID\", \"LAST_NAME\" FROM \"EMPLOYEE_TB\"\n" +
               "WHERE \"CITY\" = 'INDIANAPOLIS'\n" +
               "ORDER BY \"EMP_ID\", \"LAST_NAME\";\n";

Using a “two-dimensional” block of text

String query = """
               SELECT "EMP_ID", "LAST_NAME" FROM "EMPLOYEE_TB"
               WHERE "CITY" = 'INDIANAPOLIS'
               ORDER BY "EMP_ID", "LAST_NAME";
               """;

Shenandoah: A Low-Pause-Time Garbage Collector

This JEP essentially changes the Shenandoah Garabage collector from an experimental feature to a product feature

To enable this Garbage collection command to use is java -XX:+UseShenandoahGC

Remove the Solaris and SPARC Ports

This JEP removes the source code and build support for the Solaris/SPARC, Solaris/x64, and Linux/SPARC ports.Main reason behind dropping support for the Solaris and SPARC ports will enable contributors in the OpenJDK Community to accelerate the development of new features that will move the platform forward

Foreign-Memory Access API (Second Incubator)

This feature will Introduce an API to allow Java programs to safely and efficiently access foreign memory outside of the Java heap.

Many Java programs access foreign memory, such as Ignite, mapDB, memcached, Lucene, and Netty’s ByteBuf API. There are 3 ways to use Java API’s prior to version 15 to access foreign memory which are

But when accessing foreign memory, there is a dilemma: if one should they choose a safe but limited (and possibly less efficient) path, such as the ByteBuffer API, or should they abandon safety guarantees and embrace the dangerous and unsupported Unsafe API so this JEP aimed to provide solution for safe, supported, and efficient API for foreign memory access by introducing three main abstractions: MemorySegment, MemoryAddress, and MemoryLayout:

  • A MemorySegment models a contiguous memory region with given spatial and temporal bounds.

  • A MemoryAddress models an address. There are generally two kinds of addresses: A checked address is an offset within a given memory segment, while an unchecked address is an address whose spatial and temporal bounds are unknown, as in the case of a memory address obtained – unsafely – from native code.

  • A MemoryLayout is a programmatic description of a memory segment’s contents.

    Example snippet to create native MemorySegment and this will create a memory buffer of 100 bytes

try (MemorySegment segment = MemorySegment.allocateNative(100)) {
   ...
}

Example code:

import jdk.incubator.foreign.MemoryAddress;
import java.lang.invoke.VarHandle;
import jdk.incubator.foreign.MemoryHandles;
import java.nio.ByteOrder;
import jdk.incubator.foreign.MemorySegment;

public class ForeignMemoryAccess {

    public static void main(String[] args) {

        VarHandle handle = MemoryHandles.varHandle(
            int.class, ByteOrder.nativeOrder());

        try (MemorySegment segment = MemorySegment.allocateNative(2048)) {
            MemoryAddress base = segment.baseAddress();
            // print memory address
            System.out.println(base);
        }
    }
}

Additional reading: https://openjdk.java.net/jeps/383

Records (Second Preview)

Records are first introduced in Java 14 and this is a JEP introduced feature to support sealed types, local records, annotation on records. Records are classes that act as transparent carriers for immutable data. Records can be thought of as nominal tuples.

Main motivation for records feature is based on the complaint that “Java is too verbose” or has “too much ceremony” .Classes that are nothing more than immutable data carriers for a handful of values can be taken as a good example to demonstrate this, Properly writing a data-carrier class involves a lot of low-value, repetitive, error-prone code: constructors, accessors, equals, hashCode, toString, etc

Example: (Before Records)

public class Message {

  private final String message;
  private final long dimensionl;

   Message(String message, long dimensionl) {
    this.message = message;
    this.dimensionl = dimensionl;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Message message1 = (Message) o;
    return dimensionl == message1.dimensionl &&
        Objects.equals(message, message1.message);
  }

  @Override
  public int hashCode() {
    return Objects.hash(message, dimensionl);
  }

  @Override
  public String toString() {
    return "Message{" +
        "message='" + message + '\'' +
        ", dimensionl=" + dimensionl +
        '}';
  }
}

Omitting some methods such as equals leads to wrong behaviour during comparison of objects and difficult to debug.

Records are aimed to solve some these problems. They are a new kind of class in java language and its purpose is to declare that a small group of variables is to be regarded as a new kind of entity. A record declares its state – the group of variables – and commits to an API that matches that state

Example: (Using Records)

record Message(String message, long dimensionl) { }

Record anatomy

record specifies a name, a header, and a body. The header lists the components of the record, which are the variables that make up its state

Example

record Point(int x, int y) { }

Name here is Point

Header is x and y

Body is content between curly braces

record by default comes with four standard members

  • public accessor method with the same name and return type as the component, and a private final field with the same type as the component
  • Canonical constructor
  • equals and hashCode methods
  • toString

Records and Sealed Types

The combination of records and sealed types is sometimes referred to as algebraic data types.

Records allow to express product types, and sealed types allow to express sum types

Example:

package com.example.expression;

public sealed interface Expr
    permits ConstantExpr, PlusExpr, TimesExpr, NegExpr {...}

public record ConstantExpr(int i)       implements Expr {...}
public record PlusExpr(Expr a, Expr b)  implements Expr {...}
public record TimesExpr(Expr a, Expr b) implements Expr {...}
public record NegExpr(Expr e)           implements Expr {...}

Deprecate RMI Activation for Removal

Since Distributed systems have been based on web technology for a very long time. Concerns about traversing firewalls, filtering requests, authentication, and security have all been addressed in the web services space. Lazy instantiation of resources is handled by load balancers, orchestration, and containers. None of these mechanisms is present in the RMI Activation model for distributed systems and Java team found no evidence of any new applications being written to use RMI Activation.

RMI Activation was made optional in Java 8 and removed in Java 15

Disable and Deprecate Biased Locking

This is a legacy synchronization optimization of biased locking, which is costly to maintain hence JDK teams are disabling it by default.

Legacy collection API’s such as Hashtable and Vector made use of these performance gains but in the current trend most newer applications generally use the non-synchronized collections such as HashMap and ArrayList or even more-performant concurrent data structures, introduced in Java 5, for multi-threaded scenarios

With this JEP, biased locking will no longer be enabled when HotSpot is started unless -XX:+UseBiasedLocking is set on the command line.

More reading material:https://openjdk.java.net/jeps/374

Remove the Nashorn JavaScript Engine

This is removal from the Java and it should not affect the way javax.scriptAPI

More reading material: https://openjdk.java.net/jeps/372