Friday, October 11, 2024

JDK-23: the messenger?

It's been a little less than few weeks since JDK-23 was released and we have not covered it yet! To be fair, this is not a big deal, each new release gets more and more attention, but nonetheless! And while this one in particular may not be looking too exciting (well, mostly all the features are in preview), it is a messenger of what is coming next (and that is really thrilling).

Anyway, there are quite a few things that JDK-23 delivers.

Along with the new features, there are some disruptive changes as well, notably:

On the bright side, there are a number of enhancements, bug fixes, tooling and GC improvements that are worth mentioning:

The changes on the standard library API side are quite moderate:

On the final note, it is good time to go over some security related updates that were introduced into JDK-23 (for more details, please check JDK 23 Security Enhancements):

If anything, JDK-23 is a solid release, ready for prime time. From the changes perfective, it looks low risk as well, so if you skipped JDK-22 for some reasons, JDK-23 may be the one.

I πŸ‡ΊπŸ‡¦ stand πŸ‡ΊπŸ‡¦ with πŸ‡ΊπŸ‡¦ Ukraine.

Friday, August 30, 2024

Apache CXF at speed of ... native!

GraalVM has been around for quite a while, steadily making big waves in OpenJDK community (looking at you, JEP 483: Ahead-of-Time Class Loading & Linking). It is wonderful piece of JVM engineering that gave birth to new generation of the frameworks like Quarkus, Helidon and Micronaut, just to name a few.

But what about the old players, like Apache CXF? A large number of applications and services were built on top of it, could those benefit from GraalVM, and particularly native image compilation? The answer to this question used to vary a lot, but thanks to steady progress, GraalVM strives to make it as frictionless as possible.

In today's post, we are going to build a sample Jakarta RESTful web service using Apache CXF and Jakarta XML Binding, and compile it to native image with GraalVM Community 21.0.2+13.1.

Let us start off with the data model, which consists of a single POJO, class Customer.

import jakarta.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "Customer")
public class Customer {
    private long id;
    private String name;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

The class CustomerResource, a minimal Jakarta RESTful web service implementation, exposes a few endpoints to manage Customers, for simplicity - the state is stored in memory.

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;

@Path("/")
@Produces(MediaType.APPLICATION_XML)
public class CustomerResource {
    private final AtomicInteger id = new AtomicInteger();
    private final Map<Long, Customer> customers = new HashMap<>();

    @GET
    @Path("/customers")
    public Collection<Customer> getCustomers() {
        return customers.values();
    }

    @GET
    @Path("/customers/{id}")
    public Response getCustomer(@PathParam("id") long id) {
        final Customer customer = customers.get(id);
        if (customer != null) {
            return Response.ok(customer).build();
        } else {
            return Response.status(Status.NOT_FOUND).build();
        }
    }

    @POST
    @Path("/customers")
    public Response addCustomer(Customer customer) {
        customer.setId(id.incrementAndGet());
        customers.put(customer.getId(), customer);
        return Response.ok(customer).build();
    }

    @DELETE
    @Path("/customers/{id}")
    public Response deleteCustomer(@PathParam("id") long id) {
        if (customers.remove(id) != null) {
            return Response.noContent().build();
        } else {
            return Response.status(Status.NOT_FOUND).build();
        }
    }
}

The last piece we need is to have running web container to host the CustomerResource service. We are going to use Eclipse Jetty but any other HTTP transport supported by Apache CXF will do the job.

import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;

public class Server {
    public static org.apache.cxf.endpoint.Server create() {
        final JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
        sf.setResourceClasses(CustomerResource.class);
        sf.setResourceProvider(CustomerResource.class, new SingletonResourceProvider(new CustomerResource()));
        sf.setAddress("http://localhost:9000/");
        return sf.create();
    }
    
    public static void main(String[] args) throws Exception {
        var server = create();
        server.start();
    }
}

Literally, this is all we need from the implementation perspective. The Apache Maven dependencies list is limited to handful of those:

<dependencies>
	<dependency>
		<groupId>org.apache.cxf</groupId>
		<artifactId>cxf-rt-transports-http</artifactId>
		<version>4.0.5</version>
	</dependency>
	<dependency>
		<groupId>org.apache.cxf</groupId>
		<artifactId>cxf-rt-transports-http-jetty</artifactId>
		<version>4.0.5</version>
	</dependency>
	<dependency>
		<groupId>org.apache.cxf</groupId>
		<artifactId>cxf-rt-frontend-jaxrs</artifactId>
		<version>4.0.5</version>
	</dependency>
	<dependency>
		<groupId>ch.qos.logback</groupId>
		<artifactId>logback-classic</artifactId>
		<version>1.5.7</version>
	</dependency>
</dependencies>

Cool, so what is next? The GraalVM project provides Native Build Tools to faciliate building native images, including dedicated Apache Maven plugin. However, if we just add the plugin into the build, the resulting native image won't be functionable, even if the build succeeds:

$./target/cxf-jax-rs-graalvm-server

Exception in thread "main" java.lang.ExceptionInInitializerError
        at java.base@21.0.2/java.lang.Class.ensureInitialized(DynamicHub.java:601)
        at com.example.jaxrs.graalvm.Server.create(Server.java:27)
        at com.example.jaxrs.graalvm.Server.main(Server.java:35)
        at java.base@21.0.2/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: java.util.MissingResourceException: Can't find bundle for base name org.apache.cxf.jaxrs.Messages, locale en
        at java.base@21.0.2/java.util.ResourceBundle.throwMissingResourceException(ResourceBundle.java:2059)
        at java.base@21.0.2/java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1697)
        at java.base@21.0.2/java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1600)
        at java.base@21.0.2/java.util.ResourceBundle.getBundle(ResourceBundle.java:1283)
        at org.apache.cxf.common.i18n.BundleUtils.getBundle(BundleUtils.java:94)
        at org.apache.cxf.jaxrs.AbstractJAXRSFactoryBean.<clinit>(AbstractJAXRSFactoryBean.java:69)
        ... 4 more

Why is that? GraalVM operates under closed world assumption: all classes and all bytecodes that are reachable at run time must be known at build time. Since a majority of the frameworks, Apache CXF included, does not comply with such assumptions, GraalVM needs some help: tracing agent. The way we are going to let GraalVM capture all necessary metadata is pretty straightforward:

  • add test cases which exercise the service logic (more is better)
  • run test suite using tracing agent instrumentation
  • build the native image using the metadata collected by the tracing agent

If that sounds like a plan to you, let us add the test case first:

import java.io.InputStream;
import java.io.IOException;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.Response;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;

public class ServerTest {
    private org.apache.cxf.endpoint.Server server;
    
    @BeforeEach
    public void setUp() {
        server = Server.create();
        server.start();
    }
    
    @Test
    public void addNewCustomer() throws IOException {
        var client = ClientBuilder.newClient().target("http://localhost:9000/customers");
        try (InputStream in = getClass().getResourceAsStream("/add_customer.xml")) {
            try (Response response = client.request().post(Entity.xml(in))) {
                assertThat(response.getStatus(), equalTo(200));
            }
        }
    }

    @Test
    public void listCustomers() {
        var client = ClientBuilder.newClient().target("http://localhost:9000/customers");
        try (Response response = client.request().get()) {
            assertThat(response.getStatus(), equalTo(200));
        }
    }

    @AfterEach
    public void tearDown() {
        server.stop();
        server.destroy();
    }
}

Awesome, with tests in place, we could move on and integrate Native Build Tools into our Apache Maven build. It is established practice to have a dedicated profile for native image since the process could take quite a lot of time (and resources):

<profiles>
	<profile>
		<id>native-image</id>
		<activation>
			<property>
				<name>native</name>
			</property>
		</activation>
		<build>
			<plugins>
				<plugin>
					<groupId>org.graalvm.buildtools</groupId>
					<artifactId>native-maven-plugin</artifactId>
					<extensions>true</extensions>
					<version>0.10.2</version>
					<executions>
						<execution>
							<goals>
								<goal>compile-no-fork</goal>
							</goals>
							<phase>package</phase>
						</execution>
					</executions>
					<configuration>
						<agent>
							<enabled>true</enabled>
							<defaultMode>direct</defaultMode>
							<modes>
								<direct>config-output-dir=${project.build.directory}/native/agent-output</direct>
							</modes>
						</agent>
						<mainClass>com.example.jaxrs.graalvm.Server</mainClass>
						<imageName>cxf-jax-rs-graalvm-server</imageName>
						<buildArgs>
							<buildArg>--enable-url-protocols=http</buildArg>
							<buildArg>--no-fallback</buildArg>
							<buildArg>-Ob</buildArg>
						</buildArgs>
						<metadataRepository>
							<enabled>false</enabled>
						</metadataRepository>
						<resourcesConfigDirectory>${project.build.directory}/native</resourcesConfigDirectory>
					</configuration>
				</plugin>
			</plugins>
		</build>
	</profile>
</profiles>

It may look a bit complicated but fear not. The first thing to notice is that we configure tracing agent in the <agent> ... </agent> section. The captured metadata is going to be dumped into ${project.build.directory}/native/agent-output folder. Later on, the native image builder will refer to it as part of the <resourcesConfigDirectory> ... </resourcesConfigDirectory> configuration option. The profile is activated by the presence of native property.

Time to see each step in action! First thing first, run tests and capture the metadata:

$ mvn clean -Dnative test

...

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  6.977 s
[INFO] Finished at: 2024-08-30T15:50:55-04:00
[INFO] ------------------------------------------------------------------------

If we list the content of the target/native folder, we should see something like that:

$ tree target/native/

target/native/
└── agent-output
    ├── agent-extracted-predefined-classes
    ├── jni-config.json
    ├── predefined-classes-config.json
    ├── proxy-config.json
    ├── reflect-config.json
    ├── resource-config.json
    └── serialization-config.json    

If curious, you could inspect the content of each file, since it is just JSON, but we are going to proceed to the next step right away:

$ mvn -Dnative -DskipTests package

GraalVM Native Image: Generating 'cxf-jax-rs-graalvm-server' (executable)...
========================================================================================================================
Warning: Could not resolve org.junit.platform.launcher.TestIdentifier$SerializedForm for serialization configuration.
Warning: Could not resolve org.junit.platform.launcher.TestIdentifier$SerializedForm for serialization configuration.
[1/8] Initializing...                                                                                    (5.7s @ 0.10GB)
 Java version: 21.0.2+13, vendor version: GraalVM CE 21.0.2+13.1
 Graal compiler: optimization level: b, target machine: x86-64-v3
 C compiler: gcc (linux, x86_64, 9.4.0)
 Garbage collector: Serial GC (max heap size: 80% of RAM)
 2 user-specific feature(s):
 - com.oracle.svm.thirdparty.gson.GsonFeature
 - org.eclipse.angus.activation.nativeimage.AngusActivationFeature
------------------------------------------------------------------------------------------------------------------------
Build resources:
 - 9.99GB of memory (64.5% of 15.49GB system memory, determined at start)
 - 16 thread(s) (100.0% of 16 available processor(s), determined at start)
[2/8] Performing analysis...  [*****]                                                                   (31.2s @ 1.59GB)
   12,363 reachable types   (85.2% of   14,503 total)
   21,723 reachable fields  (63.2% of   34,385 total)
   61,933 reachable methods (57.6% of  107,578 total)
    3,856 types,   210 fields, and 2,436 methods registered for reflection
       62 types,    69 fields, and    55 methods registered for JNI access
        4 native libraries: dl, pthread, rt, z
[3/8] Building universe...                                                                               (4.8s @ 1.85GB)
[4/8] Parsing methods...      [**]                                                                       (3.0s @ 1.16GB)
[5/8] Inlining methods...     [***]                                                                      (2.2s @ 1.35GB)
[6/8] Compiling methods...    [*****]                                                                   (25.4s @ 1.89GB)
[7/8] Layouting methods...    [***]                                                                      (6.2s @ 1.49GB)
[8/8] Creating image...       [***]                                                                      (7.5s @ 2.03GB)
  32.41MB (49.57%) for code area:    38,638 compilation units
  30.78MB (47.08%) for image heap:  325,764 objects and 152 resources
   2.19MB ( 3.35%) for other data
  65.38MB in total

...

========================================================================================================================
Finished generating 'cxf-jax-rs-graalvm-server' in 1m 26s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:30 min
[INFO] Finished at: 2024-08-30T15:58:42-04:00
[INFO] ------------------------------------------------------------------------

And we should end up with a fully functional executable, let us make sure this is the case:

$ ./target/cxf-jax-rs-graalvm-server

Aug 30, 2024 4:03:22 PM org.apache.cxf.endpoint.ServerImpl initDestination
INFO: Setting the server's publish address to be http://localhost:9000/
16:03:22.987 [main] INFO  org.eclipse.jetty.server.Server -- jetty-11.0.22; built: 2024-06-27T16:27:26.756Z; git: e711d4c7040cb1e61aa68cb248fa7280b734a3bb; jvm 21.0.2+13-jvmci-23.1-b30
16:03:22.993 [main] INFO  o.e.jetty.server.AbstractConnector -- Started ServerConnector@5725648b{HTTP/1.1, (http/1.1)}{localhost:9000}
16:03:22.994 [main] INFO  org.eclipse.jetty.server.Server -- Started Server@1f9511a6{STARTING}[11.0.22,sto=0] @24ms
16:03:22.994 [main] INFO  o.e.j.server.handler.ContextHandler -- Started o.a.c.t.h.JettyContextHandler@375bd1b5{/,null,AVAILABLE}

If we open up another terminal window and run curl from the command line, we should be hitting the instance of our service and getting successful responses back:

$curl http://localhost:9000/customers -H "Content-Type: application/xml" -d @src/test/resources/add_customer.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Customer>
	<id>3</id>
    <name>Jack</name>
</Customer>

All the credits go to GraalVM team! Before we wrap up, you might be asking yourself if this the only way? And the short answer is "no": ideally, you should be able to add Native Build Tools and be good to go. The GraalVM Reachability Metadata Repository is the place that enables users of GraalVM native image to share and reuse metadata for libraries and frameworks in the Java ecosystem. Sadly, Apache CXF is not there just yet ... as many others.

The complete project sources are available on Github.

I πŸ‡ΊπŸ‡¦ stand πŸ‡ΊπŸ‡¦ with πŸ‡ΊπŸ‡¦ Ukraine.

Thursday, May 30, 2024

JDK release cadence and the pain of endless technical debt

The shiny new code you are writing today is the legacy one tomorrow: this is probably the only flaw that new JDK release cadence brings to the table. Well, with all the respect, this is the price to pay to keep up with the pace of innovation, right? Yes, but your mileage may vary ... a lot.

To put things in perspective, let us start with a few examples, all taken from the open-source projects under Apache Software Foundation umbrella. The first one we are going to look at is switch statements, typically used like that:

@Override
protected int nextChild() {
    ElementFrame<Node, Node> frame = getCurrentFrame();
    if (frame.currentChild == null) {
        content = getCurrentNode().getFirstChild();
    } else {
        content = frame.currentChild.getNextSibling();
    }

    frame.currentChild = content;
    switch (content.getNodeType()) {
        case Node.ELEMENT_NODE:
            return START_ELEMENT;
        case Node.TEXT_NODE:
            return CHARACTERS;
        case Node.COMMENT_NODE:
            return COMMENT;
        case Node.CDATA_SECTION_NODE:
            return CDATA;
        case Node.ENTITY_REFERENCE_NODE:
            return ENTITY_REFERENCE;
        case Node.PROCESSING_INSTRUCTION_NODE:
            return PROCESSING_INSTRUCTION;
        default:
            throw new IllegalStateException("Found type: " + content.getClass().getName());
    }
}

Is it something you would type these days on modern JDK (and by modern, I would safely assume at least JDK-21), that has JEP-361: Switch Expressions incorporated? I doubt that, the switch expression is much better fit here:

@Override
protected int nextChild() {
    ElementFrame<Node, Node> frame = getCurrentFrame();
    if (frame.currentChild == null) {
        content = getCurrentNode().getFirstChild();
    } else {
        content = frame.currentChild.getNextSibling();
    }

    frame.currentChild = content;
    return switch (content.getNodeType()) {
        case Node.ELEMENT_NODE -> START_ELEMENT;
        case Node.TEXT_NODE -> CHARACTERS;
        case Node.COMMENT_NODE -> COMMENT;
        case Node.CDATA_SECTION_NODE -> CDATA;
        case Node.ENTITY_REFERENCE_NODE -> ENTITY_REFERENCE;
        case Node.PROCESSING_INSTRUCTION_NODE -> PROCESSING_INSTRUCTION;
        default -> throw new IllegalStateException("Found type: " + content.getClass().getName());
    };
}

If that is not convincing, another example would clear any doubts up. Here we have quite straightforward SimplePrincipal class (data class as we would normally call such classes):

public class SimplePrincipal implements Principal, Serializable {
    private static final long serialVersionUID = -5171549568204891853L;

    private final String name;

    public SimplePrincipal(String name) {
        if (name == null) {
            throw new IllegalArgumentException("Principal name can not be null");
        }
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public boolean equals(Object obj) {
        if (!(obj instanceof SimplePrincipal)) {
            return false;
        }

        return name.equals(((SimplePrincipal)obj).name);
    }

    public int hashCode() {
        return name.hashCode();
    }

    public String toString() {
        return name;
    }
}

With the record classes, introduced by JEP 395: Records, we have much better tool at our disposal to model data classes:

public record SimplePrincipal(String name) implements Principal, Serializable {
    public SimplePrincipal {
        if (name == null) {
            throw new IllegalArgumentException("Principal name can not be null");
        }
    }
    
    @Override
    public String getName() {
        return name;
    }
}

The reduction in boilerplate code required is just astonishing. The next power feature (that could be actually used along with records) we are going to look at is sealed classes, interfaces and records, introduced by JEP 409: Sealed Classes. To showcase it, let us take a look at this (simplified) hierarchy of classed:

/**
 * This class is the base class for SSL/TLS parameters that are common
 * to both client and server sides.
 */
public class TLSParameterBase {
    protected static final Collection<String> DEFAULT_HTTPS_PROTOCOLS = 
        Arrays.asList(
            "TLSv1", 
            "TLSv1.1", 
            "TLSv1.2", 
            "TLSv1.3" 
        ); 
        
    // Other members
}

/**
 * This class extends {@link TLSParameterBase} with client-specific
 * SSL/TLS parameters.
 *
 */
public class TLSClientParameters extends TLSParameterBase {
   // TLSClientParameters class members
}

/**
 * This class extends {@link TLSParameterBase} with service-specific
 * SSL/TLS parameters.
 *
 */
public class TLSServerParameters extends TLSParameterBase {
   // TLSServerParameters class members
}

The problem with such design is that anyone could subclass TLSParameterBase (yes, we could make it package private but that would lead to inability to reference it outside of the package), violating the designer's invariants that only server and client ones are expected to exist.

/**
 * This class is the base class for SSL/TLS parameters that are common
 * to both client and server sides.
 */
public sealed class TLSParameterBase permits TLSClientParameters, TLSServerParameters {
    protected static final Collection<String> DEFAULT_HTTPS_PROTOCOLS = 
        Arrays.asList(
            "TLSv1", 
            "TLSv1.1", 
            "TLSv1.2", 
            "TLSv1.3" 
        ); 
        
    // Other members
}

/**
 * This class extends {@link TLSParameterBase} with client-specific
 * SSL/TLS parameters.
 *
 */
public final class TLSClientParameters extends TLSParameterBase {
   // TLSClientParameters class members
}

/**
 * This class extends {@link TLSParameterBase} with service-specific
 * SSL/TLS parameters.
 *
 */
public final class TLSServerParameters extends TLSParameterBase {
   // TLSServerParameters class members
}

The new sealed keyword at the TLSParameterBase class declaration level along with permits clause solves this design flaw once and for all (we could have pushed the refactoring even further and convert TLSParameterBase to record).

Let us move on to a slightly different subject: XML/JSON/YAML/(you name it) as Java Strings.

public class RequestParserUnitTest {
    private static final String USE_KEY_X509_REFERENCE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
        + "<wst:RequestSecurityToken xmlns:wst=\"http://docs.oasis-open.org/ws-sx/ws-trust/200512\">"
        + "<wst:TokenType>http://schemas.xmlsoap.org/ws/2005/02/sc/sct</wst:TokenType>"
        + "<wst:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issuelt;/wst:RequestType>"
        + "<wst:UseKey>"
        + "<wsse:SecurityTokenReference "
        + "xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">"
        + "<wsse:Reference URI=\"#x509\">lt;/wsse:Reference>lt;/wsse:SecurityTokenReference>"
        + "</wst:UseKey>" 
        + "</wst:RequestSecurityToken>";
        
    // Other members      
}

Admittedly, it looks messy and unmanageable (write once, never touch again). More to that, since this is the unit test case, troubleshooting any failing test cases caused by XML/JSON/... changes is a nightmare. How about using text blocks (multi-line strings) instead, the gem brought to Java language by https://openjdk.org/jeps/378:

public class RequestParserUnitTest {
    private static final String USE_KEY_X509_REFERENCE = """
            <?xml version="1.0" encoding="UTF-8"?>"
            <wst:RequestSecurityToken xmlns:wst="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
               <wst:TokenType>http://schemas.xmlsoap.org/ws/2005/02/sc/sct</wst:TokenType>
               <wst:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</wst:RequestType>
               <wst:UseKey>
                  <wsse:SecurityTokenReference xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
                      <wsse:Reference URI="#x509"></wsse:Reference>
                  </wsse:SecurityTokenReference>
              </wst:UseKey>
          </wst:RequestSecurityToken>
      """;

    // Other members      
}

Although the usage of instanceof is often considered as an anti-pattern (and lack of proper design), it is pervasive and sneaks into most (if not all) Java codebases.

public static Long getLong(Message message, String key) {
    Object o = message.getContextualProperty(key);
    if (o instanceof Long) {
        return (Long)o;
    } else if (o instanceof Number) {
        return ((Number)o).longValue();
    } else if (o instanceof String) {
        return Long.valueOf(o.toString());
    }
    return null;
}

The introduction of JEP 394: Pattern Matching for instanceof did at least make the instanceof constructs less verbose and more readable:

public static Long getLong(Message message, String key) {
    Object o = message.getContextualProperty(key);
    if (o instanceof Long l) {
        return l;
    } else if (o instanceof Number n) {
        return n.longValue();
    } else if (o instanceof String s) {
        return Long.valueOf(s);
    }
    return null;
}

If we want to make it even more compact, switch expression could help us once again, thanks to enhancements introduced by JEP 441: Pattern Matching for switch:

public static Long getLong(Message message, String key) {
    return switch (message.getContextualProperty(key)) {
        case Long l -> l;
        case Number n ->  n.longValue();
        case String s -> Long.valueOf(s);
        default -> null;
    };
}

The Java language is changing fast, some may say very fast (and we haven't even talked about JVM and standard library changes!). That brings a lot of benefits but at the same time, poses many challenges. Thankfully, the strict compatibility policy that Java language designers prioritize over everything else means that you don't need to rewrite your code, it will still work. It just becomes legacy ... it becomes yet another kind of technical debt to pay ...

I πŸ‡ΊπŸ‡¦ stand πŸ‡ΊπŸ‡¦ with πŸ‡ΊπŸ‡¦ Ukraine.

Saturday, March 23, 2024

JDK-22: The JNI's grave?

Another six months passed and it is about time for a new JDK release: without further ado, please meet JDK-22. The theme of this release is obviously Foreign Function & Memory API that becomes generally available after numerous preview cycles. So what else is there?

  • JEP-454: Foreign Function & Memory API: introduces an API by which Java programs can interoperate with code and data outside of the Java runtime. By efficiently invoking foreign functions (i.e., code outside the JVM), and by safely accessing foreign memory (i.e., memory not managed by the JVM), the API enables Java programs to call native libraries and process native data without the brittleness and danger of JNI.

    By all means, Foreign Function & Memory API (or FFM shortly), was eagerly awaited. For a long time JNI was the only mechanism to access native code on JVM and it has gained pretty infamous reputation of being brittle and outdated. FFM has emerged as a (substantially) better alternative and, after many rounds of previews, the API is finalized in JDK-22.

  • JEP-423: Region Pinning for G1: reduces latency by implementing region pinning in G1, so that garbage collection need not be disabled during Java Native Interface (JNI) critical regions.

  • JEP-456: Unnamed Variables & Patterns: enhances the Java programming language with unnamed variables and unnamed patterns, which can be used when variable declarations or nested patterns are required but never used. Both are denoted by the underscore character, _.

    To be fair, the positive impact of this small language change on the readability of Java programs is paramount. Familiar to Scala developers for years, it is here for Java developers now.

            if (content instanceof String s && s.startsWith("{")) {
                parseJson(s);
            } else if (content instanceof String s && s.startsWith("<")) {
                parseXml(s);
            } else if (content instanceof String _) {
                throw new IllegalArgumentException("The content type is not detected");
            };
      

    It becomes even more evident with records:

    
         sealed interface Response permits NoContentResponse, ContentResponse {}
        record NoContentResponse(int status) implements Response {}
        record ContentResponse(int status, byte[] content) implements Response {}
    
        public int status(final Response response) {
            return switch(response) {
                case NoContentResponse(var status) -> status;
                case ContentResponse(var status, _) -> status;
            };
        }
          

    And lamba functions:

        BiFunction<String, Number, String> f = (_, n) -> n.toString();
        
  • JEP-458: Launch Multi-File Source-Code Programs: enhances the java application launcher to be able to run a program supplied as multiple files of Java source code. This will make the transition from small programs to larger ones more gradual, enabling developers to choose whether and when to go to the trouble of configuring a build tool.

    This is a logical continuation of the JEP 330: Launch Single-File Source-Code Programs, delivered in JDK-11. With this change, Java could seriously challenge the status quo of the established scripting languages.

Couple of new and refined preview language features and APIs are also made into release, just briefly mentioning them here (and we would get back to them once finalized).

Every new JDK release comes with a long list of bugfixes, and in case of JDK-22, there are quite a few worth mentioning:

Moving off from bugs and regressions, let us take a look at the interesting new features that JDK-22 delivers across the board:

Last but not least, let us talk about the API changes (standard library) that went into this release.

From the security perspective, there are couple of notable changes to highlight (but please, do not hesitate to check out JDK 22 Security Enhancements for more details):

To finish up, it will be useful to mention a few regressions that ended up in JDK-22 release, the fixes are already scheduled for the upcoming major or patch releases:

Some may say that JDK-22 is a boring release, but I personally disagree: FFM APIs and formalizing _ usage are all but not boring features.

I πŸ‡ΊπŸ‡¦ stand πŸ‡ΊπŸ‡¦ with πŸ‡ΊπŸ‡¦ Ukraine.

Tuesday, December 26, 2023

When concurrency bites (yet again!): class initialization deadlocks

Concurrent and parallel programming on JVM platform has never been easy: yes, it is significantly safer and simpler than in most programming languages (thanks to outstanding concurrency support by Java language, standard library and JVM) but still, surprises pop up from time to time.

In today's post we are going to learn how a seemingly innocent implementation may intermittently deadlock during the class initialization under some circumstances. To begin, here is the Options class we are going to work with along the way.

package com.example;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public final class Options {
    public final static Options EMPTY = new Builder().build();
    private final Map<String, String> options;
    
    public Options(final Map<String, String> options) {
        this.options = new HashMap<>(Objects.requireNonNull(options));
    }

    @Override
    public String toString() {
        return "Options=" + options.toString();
    }

    public static class Builder {
        public static final Options EMPTY = new Builder().build();
        private final Map<String, String> options = new HashMap<>();

        public Builder option(final String name, final String value) {
            this.options.put(name, value);
            return this;
        }

        public Options build() {
            return new Options(options);
        }
    }
}

The snippet above implements a variation of the Builder pattern (in this case, for Options class). Although the sample is somewhat made up, the similarities to the existing implementations aren't (for example, please check Parts of Rest High-Level Client not thread-safe out). At a glance, it seems to be no-brainer, the code compiles and runs perfectly fine (the console output serves as a proof in this case).

    public static void main(String[] args) {
        System.out.println("New instance: " + new Options.Builder().build());
        System.out.println("EMPTY Options instance: " + Options.EMPTY);
        System.out.println("EMPTY Options.Builder instance: " + Options.Builder.EMPTY);
    }

Once executed, we should see a few lines printed out.

New instance: Options={}
EMPTY Options instance: Options={}
EMPTY Options.Builder instance: Options={}

The attentive reviewer may spot something fishy about this implementation, specifically related to EMPTY static fields: there is an explicit bidirectional (or better to say, circular) dependency between Options and Options.Builder classes. But JVM is able to handle that, non issue, right? Well, yes and no, and to understand why, let us take a look at the variation of the initialization sequence that is triggered concurrently:

    public static void main(String[] args) {
        try (ExecutorService executor = Executors.newFixedThreadPool(2)) {
            executor.submit(() -> System.out.println("New instance: " + new Options.Builder().build()));
            executor.submit(() -> System.out.println("EMPTY Options instance: " + Options.EMPTY));
        }
    }

Surprisingly (or not?), the execution of this code intermittently hangs the JVM. If we look into the thread dump, the reason becomes very clear (thanks to JDK-8288064: Class initialization locking, the output has been redacted a bit to highlight the clues).

"pool-1-thread-1" #29 [8432] prio=5 os_prio=0 cpu=0.00ms elapsed=499.54s allocated=7800B defined_classes=1 tid=0x000002609f22b680 nid=8432 waiting on condition  [0x000000be184fe000]
   java.lang.Thread.State: RUNNABLE
	at com.example.Options$Builder.build(Options.java:35)
	- waiting on the Class initialization monitor for com.example.Options
	at com.example.Options$Builder.<clinit>(Options.java:26)
    ...

"pool-1-thread-2" #30 [19688] prio=5 os_prio=0 cpu=0.00ms elapsed=499.54s allocated=5184B defined_classes=1 tid=0x000002609f233e40 nid=19688 waiting on condition  [0x000000be185fe000]
   java.lang.Thread.State: RUNNABLE
	at com.example.Options.<clinit>(Options.java:9)
	- waiting on the Class initialization monitor for com.example.Options$Builder
    ...

The Options and Options.Builder classes deadlock during initialization (and indeed, Options needs Options.Builder to initialize EMPTY static field however Options.Builder needs Options to initialize own EMPTY static field). The JLS (Java Language Specification) is very clear on that (but how many of us have read the specification anyway?):

Because the Java programming language is multithreaded, initialization of a class or interface requires careful synchronization, since some other thread may be trying to initialize the same class or interface at the same time. There is also the possibility that initialization of a class or interface may be requested recursively as part of the initialization of that class or interface; for example, a variable initializer in class A might invoke a method of an unrelated class B, which might in turn invoke a method of class A. The implementation of the Java Virtual Machine is responsible for taking care of synchronization and recursive initialization by using the following procedure. - 12.4.2. Detailed Initialization Procedure

This is by no means a new issue that could be solved by restructuring the code, it has been known for years (JDK-4891511: Deadlock in class initialization specification, JLS 2nd ed. 12.4.2) but it still bites and not easy to troubleshoot (JDK-8059913: Deadlock finder is unable to find deadlocks caused by <clinit>). Luckily, JVM diagnostics is getting better and upcoming JDK-22 release will bring yet another improvement as part of JDK-8316229: Enhance class initialization logging, it should definitely help to debug apparent class initialization deadlocks (when JVM has class+init debug logging enabled using -Xlog:class+init=debug command line option).

...
[0.089s][debug][class,init] Thread "pool-1-thread-1" is initializing com.example.Options$Builder
[0.089s][debug][class,init] Thread "pool-1-thread-2" is initializing com.example.Options
[0.089s][info ][class,init] 511 Initializing 'com/example/Options$Builder' (0x000002b9dd001000) by thread "pool-1-thread-1"
[0.089s][info ][class,init] 512 Initializing 'com/example/Options' (0x000002b9dd001218) by thread "pool-1-thread-2"
[0.089s][debug][class,init] Thread "pool-1-thread-1" recursively initializing com.example.Options$Builder
[0.089s][debug][class,init] Thread "pool-1-thread-2" waiting for initialization of com.example.Options$Builder by thread "pool-1-thread-1"
[0.089s][debug][class,init] Thread "pool-1-thread-1" waiting for initialization of com.example.Options by thread "pool-1-thread-2"
...

Undoubtedly, JVM is very sophisticated piece of technology and each release (which is happening every six months these days) brings more features, bugfixes and improvements. Despite getting smarter, JVM still requires developers to be aware of its limitations and think about the code they write (would co-pilots and LLMs help with that is yet to be seen).

I πŸ‡ΊπŸ‡¦ stand πŸ‡ΊπŸ‡¦ with πŸ‡ΊπŸ‡¦ Ukraine.

Thursday, October 5, 2023

JDK-21: green threads are officially back!

The JDK-21 is there, bringing virtual threads (back) into JVM as a generally available feature (if you are old enough like myself, you might have remembered that in Java 2 releases prior to 1.3 the JVM used its own threads library, known as green threads, to implement threads in the Java platform). This is big, but what else is coming?

  • JEP-431: Sequenced Collections: introduces new interfaces to represent collections with a defined encounter order. Each such collection has a well-defined first element, second element, and so forth, up to the last element. It also provides uniform APIs for accessing its first and last elements, and for processing its elements in reverse order.

    The following new interfaces have been introduced (and retrofitted into the existing collections type hierarchy), potentially a breaking change for some library implementors:

  • JEP-439: Generational ZGC: improves application performance by extending the Z Garbage Collector (ZGC) to maintain separate generations for young and old objects. This will allow ZGC to collect young objects — which tend to die young — more frequently.

    By default, the -XX:+UseZGC command-line option selects non-generational ZGC, but to select the Generational ZGC, additional command line option -XX:+ZGenerational is required:

    $ java -XX:+UseZGC -XX:+ZGenerational ...
    

  • JEP-440: Record Patterns: enhances the Java programming language with record patterns to deconstruct record values. Record patterns and type patterns can be nested to enable a powerful, declarative, and composable form of data navigation and processing. This is certainly a huge step towards having a powerful, feature-rich pattern matching capabilities in the language:

        interface Host {}
        record TcpHost(String name, int port) implements Host {}
        record HttpHost(String scheme, String name, int port) implements Host {}
        

    The are several places the records could be deconstructed, instanceof check being one of those:

        final Host host = new HttpHost("https", "localhost", 8080);
        if (host instanceof HttpHost(var scheme, var name, var port)) {
            ... 
        } else if (host instanceof TcpHost(var name, var port)) {
            ...
        }
        
  • JEP-441: Pattern Matching for switch: enhances the Java programming language with pattern matching for switch expressions and statements. Extending pattern matching to switch allows an expression to be tested against a number of patterns, each with a specific action, so that complex data-oriented queries can be expressed concisely and safely.

    Considering the example with the records deconstruction from above, we could use record patterns in switch expressions too:

            var hostname = switch(host) {
                case HttpHost(var scheme, var name, var port) -> name;
                case TcpHost(var name, var port) -> name;
                default -> throw new IllegalArgumentException("Unknown host");
            };
        

    But the switch patterns are much more powerful, with guards to pattern case labels, null labels, etc.

            final Object obj = ... ; 
            var o = switch (obj) {
                case null ->  ... ;
                case String s -> ... ;
                case String[] a when a.length == 0 -> ... ;
                case String[] a -> ... ;
                default ->  ... ;
            }
        
  • JEP-444: Virtual Threads: introduces virtual threads to the Java Platform. Virtual threads are lightweight threads that dramatically reduce the effort of writing, maintaining, and observing high-throughput concurrent applications. The virtual threads and executors could be used along the traditional ones, following the same familiar API:

        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            executor.submit(() -> {
                    ...
            });
        }  
        

    Some of the quirks of the virtual threads we have discussed previously here and here, but there is one more: you could use them in parallel streams, but should you? The answer is a bit complicated, so referring you to Virtual Threads and Parallel Streams article if you are looking for clarity.

    The JDK tooling (like jcmd and jfr) has been updated to include the information about virtual threads where applicable.

    The jcmd thread dump lists virtual threads that are blocked in network I/O operations and virtual threads that are created by the ExecutorService interface. It does not include object addresses, locks, JNI statistics, heap statistics, and other information that appears in traditional thread dumps (as per Viewing Virtual Threads in jcmd Thread Dumps).
    Java Flight Recorder (JFR) can emit these events related to virtual threads (as per Java Flight Recorder Events for Virtual Threads):
    • jdk.VirtualThreadStart and jdk.VirtualThreadEnd (disabled by default)
    • jdk.VirtualThreadPinned (enabled by default with a threshold of 20 ms)
    • jdk.VirtualThreadSubmitFailed (enabled by default)

    It is worth noting that Oracle has published a comprehesive guide on virtual threads as par of JDK-21 documentation update.

  • JEP-449: Deprecate the Windows 32-bit x86 Port for Removal: deprecates the Windows 32-bit x86 port, with the intent to remove it in a future release.

  • JEP-451: Prepare to Disallow the Dynamic Loading of Agents: issues warnings when agents are loaded dynamically into a running JVM. These warnings aim to prepare users for a future release which disallows the dynamic loading of agents by default in order to improve integrity by default. Serviceability tools that load agents at startup will not cause warnings to be issued in any release.

    Running with -XX:+EnableDynamicAgentLoading on the command line serves as an explicit "opt-in" that allows agent code to be loaded into a running VM and thus suppresses the warning. Running with -XX:-EnableDynamicAgentLoading disallows agent code from being loaded into a running VM and can be used to test possible future behavior.

    In addition, the system property jdk.instrument.traceUsage can be used to trace uses of the java.lang.instrument API. Running with -Djdk.instrument.traceUsage or -Djdk.instrument.traceUsage=true causes usages of the API to print a trace message and stack trace. This can be used to identify agents that are dynamically loaded instead of being started on the command line with -javaagent.

  • JEP-452: Key Encapsulation Mechanism API: introduces an API for key encapsulation mechanisms (KEMs), an encryption technique for securing symmetric keys using public key cryptography. The new APIs are centered around javax.crypto.KEM and javax.crypto.KEMSpi abstractions.

  • JEP-430: String Templates (Preview): enhances the Java programming language with string templates. String templates complement Java's existing string literals and text blocks by coupling literal text with embedded expressions and template processors to produce specialized results. This is a preview language feature and API.

  • JEP-453: Structured Concurrency (Preview): simplifies concurrent programming by introducing an API for structured concurrency. Structured concurrency treats groups of related tasks running in different threads as a single unit of work, thereby streamlining error handling and cancellation, improving reliability, and enhancing observability. This is a preview language feature and API.

  • JEP-443: Unnamed Patterns and Variables (Preview): enhances the Java language with unnamed patterns, which match a record component without stating the component's name or type, and unnamed variables, which can be initialized but not used. Both are denoted by an underscore character, _. This is a preview language feature.

  • JEP-445: Unnamed Classes and Instance Main Methods (Preview): evolves the Java language so that students can write their first programs without needing to understand language features designed for large programs. Far from using a separate dialect of Java, students can write streamlined declarations for single-class programs and then seamlessly expand their programs to use more advanced features as their skills grow. This is a preview language feature.

  • JEP-446: Scoped Values (Preview): introduces scoped values, values that may be safely and efficiently shared to methods without using method parameters. They are preferred to thread-local variables, especially when using large numbers of virtual threads. This is a preview language API.

    In effect, a scoped value is an implicit method parameter. It is "as if" every method in a sequence of calls has an additional, invisible, parameter. None of the methods declare this parameter and only the methods that have access to the scoped value object can access its value (the data). Scoped values make it possible to pass data securely from a caller to a faraway callee through a sequence of intermediate methods that do not declare a parameter for the data and have no access to the data.

  • JEP-442: Foreign Function & Memory API (3rd Preview): introduces an API by which Java programs can interoperate with code and data outside of the Java runtime. By efficiently invoking foreign functions (i.e., code outside the JVM), and by safely accessing foreign memory (i.e., memory not managed by the JVM), the API enables Java programs to call native libraries and process native data without the brittleness and danger of JNI. This is a preview language API.

  • JEP-448: Vector API (6th Incubator): introduces an API to express vector computations that reliably compile at runtime to optimal vector instructions on supported CPU architectures, thus achieving performance superior to equivalent scalar computations.

Those JEPs are the themes of JDK-21 but what other features are coming? There are quite a few to unpack to be fair.

The JDK-21 changeset looks already impressive but ... we are not done yet, let us walk through the standard library changes.

From the security perspective, JDK-21 is pretty packed with enhancements. Some of them we have highlighted above but a few more deserve special mentions (if you need a comprehensive look, please check out JDK 21 Security Enhancements article):

From all perspectives, JDK-21 looks like the release worth migrating to (it is supposed to be LTS), despite the fact there are unforeseen delays announced by some vendors.

I πŸ‡ΊπŸ‡¦ stand πŸ‡ΊπŸ‡¦ with πŸ‡ΊπŸ‡¦ Ukraine.