To Java 11 and beyond - Part 1

April 21, 2020

To Java 11 and beyond - Part 1

It has been over a year since the release of Java 11, the latest LTS version of Java. Since this release, we already saw two new non-LTS releases, as part of Reinholds 6-month release cycle proposal Moving Java Forward Faster.

However, while the 6-month release cycle does encourage Java to release features frequently and prevent features to block an entire LTS release, it does create another challenge. That is, changes and features with a considerable impact could be released in between LTS releases. This sparked the following question within me: “Towards what extent would I have to follow these releases?”.

Besides that, this article series also offers the opportunity to analyze the patterns Java has adopted due to the 6-month release cycle, and their overall impact on the Java community. Especially during this period, where more and more projects are making the transition from Java 8 to 11, we start to see people getting used to this new release cycle. Using this transition period, I aim to look towards the future of Java, the community, it’s expectations and debate whether these are catered to by Java.

After all, as an “innovator” within the adoption cycle it is important to have a frame of reference in regards to the capabilities of this release cycle to truly change the way we approach Java development.

Paired with all of the above, I’m studying for my Java 11 OCP certification at the moment as part of my personal development within Sentia. Because of these factors I deemed it useful to write about some of the biggest changes in both the JDK and the Java platform between Java 8 and Java 11. At the same time I attempt to stress the importance of staying up to date with all new releases and what benefits are to be gained by evaluating the potential future of Java.

In this first installment of a two part series, I’ll focus more on two changes that I feel present a good example of what we can expect from the release cycle.

Java Platform Module System JSR (376)

Aimed to introduce a “all the way down” modular system, the Java platform module system, or jigsaw, is integrated into both the JVM as the JDK. The latter is especially valuable, as this hasn’t been addressed by other module systems living within the Java ecosystem (for example, WildFly). While Jigsaw has gained critique throughout the Java community Scott Stark’s Blog: Concerns Regarding Jigsaw, it’s refreshing to see the Java team directly competing with existing module systems. As these solutions have been mostly rejected by the market, and don’t offer a “all the way down” solution. Seeing Java ready to challenge some of the design decisions made by other module system experts shows maturity.

Before we take a shallow dive into what the Module System has to offer, it’s important to define the term module: “A module is a group of closely related packages and resources that is described through a module descriptor file”. Thus, a module allows us to explicitly declare which packages should be exported and which are for internal usage only.

To illustrate this, we will walk through a simple example. As a baseline, I’ll be using the project structure as illustrated in the following picture.

Here we have two modules, sentia.helloblog & sentia.helloworld.

For each module we defined a module descriptor, also known as a module-info.Java file. In the case of helloworld, we have 2 packages. However we only want to expose the **_Java11 _**package to other modules and keep our privatepackage for internal usage only.

To achieve this, we will explicitly export Java11 with a module descriptor:

module sentia.helloworld {
   exports com.sentia.Java11;
}

Sentia.helloworld module descriptor

As a result, the Java11 package gets exposed by our sentia.helloworld module. However, this doesn’t make it ready for use within our sentia.helloblog module quite yet. To achieve this, we have to explicitly declare in the module descriptor of sentia.helloblog that it depends on sentiahelloworld like so:

module sentia.helloblog {
   exports com.sentia.Java11blog;
   requires sentia.helloworld;
}

Sentia.helloblog module descriptor

While the above scenario does in fact enforce strong encapsulation, it also hinders the capabilities of Reflection in Java. This isn’t a bad thing in itself, since reflection opens up a whole other can of problems for developers. However, reflection is used by many big frameworks and libraries and the Module System acknowledges this. To still allow other classes to reflect on our classes when required, we can explicitly grant permission to use it freely by the reserved word open:

open module sentia.helloworld {
  exports com.sentia.Java11;
}

If we need more finer grained control, we can also indicate certain packages to be reflectable by the reserved opens.

module sentia.helloworld {
  opens com.sentia.Java11;
}

While there are more reserved and more principals around modules, I feel this gives a good impression to at least motivate the potential benefit it could have. For a more comprehensive write-up, I’d recommend sources like A Guide to Java 9 Modularity.

I’m aware that with the scenario illustrated above, it’s rather easy to define which packages are used by which module. In a real world scenario, where you are migrating from a traditional application structure to a modularized one, defining all these relationships would be a difficult task.

To minimize the (mostly mental) damage, the JDK facilitates a tool that dictates which external jars are used and which are exported: jdeps. While I won’t go into detail about how to use this tool, it’s good to realize there are options to reduce the effort in the migration process to modules.

Conclusion

Even though the module System changes a lot about how we use Java, we don’t have to worry about mandatory refactoring to migrate, as a tool is available to automatically create modules based on your application: Java 9 Modules - Automatic Modules. Regardless, you can expect your application to keep working on Java 9 through 11 as it did on Java 8 (under the premise you aren’t using deprecated features) since Modules are not a requirement.

For more information about this topic, I can highly recommend Modular Development with JDK 9 by Alex Buckley, where Alex Buckley motivates the design choices around the modular system.

JEP321: Http Client

This JEP is also part of the more significant new API’s we get with Java 11. What stood out for me about this HTTP client is that it has been introduced in Java 9 as an Incubator Module. We are expected to see more of this pattern due to the new 6 months release cycle of Java. After all, it allows developers to give the Java platform feedback on new features early, which can be used to further improve the official API 's quality.

This HTTP API brings first-class support for HTTP/2, promotion to the Websocket protocol and both async and synchronous mode out of the box. And it still supports legacy HTTP/1.

The API itself is part of the Java.net.http package and has three core components:

  • Java.net.HttpClient- A client itself, used to send requests and retrieve responses. Created through builder
  • Java.net.HttpRequest- an HTTP request. Created through builder
  • Java.net.HttpResponse- a HTTP response. This is a product of sending an httpRequest.

Let’s see how we would utilize these components:

Examples

For these following examples i’ll be using JSONPlaceholder as our targeted URI.

To be able to send requests, we need to initialize a HttpClient object first. Lucky for us, this is as straightforward as it gets:

HttpClient httpClient = HttpClient.newBuilder()
       .version(HttpClient.Version.HTTP_2) // Default setting
       .build();

This instantiates a client that uses the default configuration and the HTTP/2 protocol. This implies that the first request to an origin server will attempt to use this protocol. If the server supports it, the response will be sent with this protocol. Otherwise HTTP/1.1 will be used as a fallback.

To send a request with this client, we first have to instantiate a request object, for example a basic get request:

HttpRequest httpRequest = HttpRequest.newBuilder()
       .uri(URI.create("https://jsonplaceholder.typicode.com/todos/1"))
       .GET() //default
       .build();

HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());

System.out.println(response.statusCode());
System.out.println(response.body());

The most notable thing happening here is that we are using a BodyHandler to instruct our response what kind of data we are expecting. Note that this BodyHandler is a functional interface, meaning that we can roll our own specialized handlers when needed.

For a POST request with a body, we have to provide a BodyPublisher in order to convert the body into bytes. Other than that it’s equivalent to instantiating a get request…

HttpRequest httpPostRequest = HttpRequest.newBuilder()
       .uri(URI.create("https://jsonplaceholder.typicode.com/posts"))
       .POST(HttpRequest.BodyPublishers.ofString(json))
       .build();

Sometimes however, it’s unnecessary to send a HTTP request in a blocking manner. For these scenarios, we can utilize the httpClient instance method sendAsync(), which returns a CompletableFuture. In short, a completableFuture is a mechanism that allows us to chain subsequent actions to be triggered when it is completed. In our context the CompletableFuture is completed when an HttpResponse is received. In our example below we rewrite our blocking send for the post request to a asynchronous one:

httpClient.sendAsync(httpPostRequest, HttpResponse.BodyHandlers.ofString()).thenAccept(
       resp -> {
           System.out.println(resp.statusCode());
           System.out.println(resp.body());
       }
);

And that’s it! With relative ease we were able to send the two most used Requests and retrieve responses.

Conclusion

The key aspect to take away from this API is that we finally have a batteries included way to communicate across HTTP. While this HTTP client might fall short for more complex situations within a production environment, it should be enough for most test cases and/or POC’s.

For a more indepth article about the API and everything it has to offer, i’ll gladly refer you to A closer look at the Java 11 HTTP Client.

Final Conclusion

Even though we’ve only looked at two changes included within Java 11, we are already seeing substantial changes to the approach we’ve learned to expect from Java releases. For example, the usage of incubator modules to gather feedback from within the community feels rejuvenating and gives us developers a good picture about the path Java is taking. Besides this we are also seeing project jigsaw with changes to both the JDK and JVM, being released in Java 9. In the past, we would have had to wait for the new LTS to get our hands on such a feature. Yet, now we already have resources that can help us make the migration towards the module system a smooth journey.

All in all, looking at just these two examples sparks excitement to dive into the other changes we haven’t taken a look at yet. Be sure to look forward to the second installment of this blog, where we will look at a few more examples and take a more comprehensive look at my expectations for the future.

Alex van der Wal

Alex van der Wal

Java Developer