Java EE Update

Content:

  1. Introduction
  2. Java EE 8
    1. New JSON API's
    2. HTTP 2.0 support
  3. Jakarta EE 8
  4. Jakarta EE 9
  5. Jakarta EE 9.1
  6. Jakarta EE 10
    1. Faces (JSF) 4.0
    2. Servlet 6.0
    3. Java Persistence (JPA) 3.1
    4. Concurrency 3.0

Introduction:

This article will describe new features in Java EE after version 7.

Java EE 8:

Java EE 8 was released August 31st 2017.

I consider it a relative small release with only a few major enhancements.

New JSON API's:

Before Java EE 8 then Java did not have a standard JSON API.

JSON could be consumed and produced implicit in JAX-RS services, but not explicit using standard API.

So people had to use third party libraries like GSON.

Java EE 8 defines two new JSON API's:

Do not confuse JSON-P with JSONP (JavaScript standard to workaround same origin policies).

The API's are rather straigth forward, which a couple of examples should illustrate.

JSON-P example:

package jsonp;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.stream.Collectors;

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonReader;
import javax.json.JsonWriter;
import javax.json.JsonValue;

// JSR 353 JSON-P
public class DOMandStream {
    public static void main(String[] args) throws IOException {
        // JsonObjectBuilder
        JsonObjectBuilder ob = Json.createObjectBuilder();
        JsonObject o = ob
                       .add("iv", 123)
                       .add("xv", 123.456)
                       .add("sv", "ABC")
                       .add("a", Json.createArrayBuilder()
                                 .add(1)
                                 .add(2)
                                 .add(3))
                       .add("o", Json.createObjectBuilder().add("a", 1).add("b", 2).add("c", 3)).build();
        System.out.println(o);
        System.out.printf("{\"iv\":%d,\"xv\":%s,\"sv\":\"%s\",\"a\":[%s],\"o\":{\"a\":%d,\"b\":%d,\"c\":%d}}\n",
                          o.getJsonNumber("iv").intValue(),
                          Double.toString(o.getJsonNumber("xv").doubleValue()).replaceAll("0+$", ""),
                          o.getString("sv"),
                          o.getJsonArray("a").getValuesAs(JsonValue.class).stream().map(v -> v.toString()).collect(Collectors.joining(",")),
                          o.getJsonObject("o").getJsonNumber("a").intValue(),
                          o.getJsonObject("o").getJsonNumber("b").intValue(),
                          o.getJsonObject("o").getJsonNumber("c").intValue());
        // JsonWriter
        String s;
        try(StringWriter sw = new StringWriter()) {
            try(JsonWriter jw = Json.createWriter(sw)) {
                jw.writeObject(o);
            }
            s = sw.toString();
        }
        System.out.println(s);
        // JsonReader
        JsonObject o2;
        try(StringReader sr = new StringReader(s)) {
            try(JsonReader jr = Json.createReader(sr)) {
                o2 = jr.readObject();
            }
        }
        System.out.println(o2);
    }
}

JSON-B example:

package jsonb;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.stream.Collectors;

import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;

// JSR 367 JSON-B
public class Bind {
    public static class SubData {
        private int a;
        private int b;
        private int c;
        public SubData() {
            this(0, 0, 0);
        }
        public SubData(int a, int b, int c) {
            this.a = a;
            this.b = b;
            this.c = c;
        }
        public int getA() {
            return a;
        }
        public void setA(int a) {
            this.a = a;
        }
        public int getB() {
            return b;
        }
        public void setB(int b) {
            this.b = b;
        }
        public int getC() {
            return c;
        }
        public void setC(int c) {
            this.c = c;
        }
        @Override
        public String toString() {
            return String.format("{\"a\":%d,\"b\":%d,\"c\":%d}", a, b, c);
        }
    }
    public static class Data {
        private int iv;
        private double xv;
        private String sv;
        private int[] a;
        private SubData o;
        public Data() {
            this(0, 0.0, "", new int[0], new SubData());
        }
        public Data(int iv, double xv, String sv, int[] a, SubData o) {
            this.iv = iv;
            this.xv = xv;
            this.sv = sv;
            this.a = a;
            this.o = o;
        }
        public int getIv() {
            return iv;
        }
        public void setIv(int iv) {
            this.iv = iv;
        }
        public double getXv() {
            return xv;
        }
        public void setXv(double xv) {
            this.xv = xv;
        }
        public String getSv() {
            return sv;
        }
        public void setSv(String sv) {
            this.sv = sv;
        }
        public int[] getA() {
            return a;
        }
        public void setA(int[] a) {
            this.a = a;
        }
        public SubData getO() {
            return o;
        }
        public void setO(SubData o) {
            this.o = o;
        }
        private String aToString() {
            return "[" + Arrays.stream(a).mapToObj(Integer::toString).collect(Collectors.joining(",")) + "]";
        }
        private String xvToString() {
            return Double.toString(xv).replaceAll("0+$", "");
        }
        @Override
        public String toString() {
            return String.format("{\"a\":%s,\"iv\":%d,\"o\":%s,\"sv\":\"%s\",\"xv\":%s}", aToString(), iv, o.toString(), sv, xvToString());
        }
    }
    public static void main(String[] args) throws IOException {
        Jsonb b = JsonbBuilder.create();
        Data o = new Data(123, 123.456, "ABC", new int[] { 1, 2, 3 }, new SubData(1, 2, 3));
        System.out.println(o);
        // to JSON
        String s;
        s = b.toJson(o);
        System.out.println(s);
        try(StringWriter sw = new StringWriter()) {
            b.toJson(o, sw);
            s = sw.toString();
            System.out.println(s);
        }
        // from JSON
        Data o2;
        o2 = b.fromJson(s, Data.class);
        System.out.println(o2);
        try(StringReader sr = new StringReader(s)) {
            o2 = b.fromJson(sr, Data.class);
            System.out.println(o2);
        }
    }
}

Don't think so much about the funky toString methods - I just wanted them to output proper JSON, but that is not relevant forunderstanding JSON-B.

I like both API's. But it is deliberate that I chose to demo them in a Java SE standalone application. In my opinion it was a mistake to add them to Java EE - they should have been added to Java SE.

HTTP 2.0 support:

HTTP 1.1 goes back to 1997 and has been the standard transport on the web since then. Even though it has some downsides.

To remedy those downside the development of HTTP 2.0 was initiated in 2012. Google played a leading role in the work via its SPDY experimental protocol. The final HTTP 2.0 protocol became official standard in 2015. Today all modern browsers support HTTP 2.0.

New features to improve performance include:

The first of these are just a protocol issue between the browser and the HTTP connector. But the last one requires some support from the actual server side code.

Let us look at how server push work with a foo.html page that uses bar.jpg image.

HTTP 1.1 (and HTTP 2.0 without server push) work like:

  1. browser request foo.html
  2. server send foo.html part 1
  3. server send foo.html part 2
  4. server send foo.html part 3
  5. browser parse foo.html and determines that it needs bar.jpg
  6. browser request bar.jpg
  7. server send bar.jpg part 1
  8. server send bar.jpg part 2
  9. server send bar.jpg part 3

HTTP 2.0 with server push work like:

  1. browser request foo.html
  2. server send "you need bar.jpg and I will send it in stream 2"
  3. server send foo.html part 1 (in stream 1)
  4. server send bar.jpg part 1 (in stream 2)
  5. server send foo.html part 2 (in stream 1)
  6. server send bar.jpg part 2 (in stream 2)
  7. server send foo.html part 3 (in stream 1)
  8. server send bar.jpg part 3 (in stream 2)
  9. browser parse foo.html and determines that it needs bar.jpg but note that it already got it

This can increase performance.

So let us see how this can be done in a Java EE web application.

It is actually very easy. Java EE added a PushBuilder class for doing this.

Let us start by looking at a simple JSP page:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<link rel="stylesheet" type="text/css" href="general.css">
<script src="d1.js" type="text/javascript"></script>
<script src="d2.js" type="text/javascript"></script>
<script src="d3.js" type="text/javascript"></script>
<script type="text/javascript">
window.onload = function(e) { 
    setd1();
    setd2();
    setd3();
}
</script>
<title>Demo</title>
</head>
<body>
<h1>Demo</h1>
<h2>Static content:</h2>
<p>
It works!
</p>
<h2>Dynamic content:</h2>
<div id="d1"></div>
<div id="d2"></div>
<div id="d3"></div>
<h2>Graphics:</h2>
<img src="images/logo_256_black.gif" alt="Black logo">
<img src="images/logo_256_blue.gif" alt="Blue logo">
<img src="images/logo_256_gray.gif" alt="Gray logo">
<img src="images/logo_256_green.gif" alt="Green logo">
<img src="images/logo_256_red.gif" alt="Red logo">
<img src="images/logo_256_yellow.gif" alt="Yellow logo">
</body>
</html>

We see that the JSP page uses 1 CSS file, 3 JS files and 6 GIF files. The example JSP page is obviously not realistic, but using 10 other files is very realistic. In fact many modern pages use a lot more (over 100 is sometimes seen).

Loading this JSP page result in ten HTTP/1.1 requests.

Now we enable server push of the used resources:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%
if(request.newPushBuilder() != null) {
    request.newPushBuilder().path("general.css").push();
    request.newPushBuilder().path("d1.js").push();
    request.newPushBuilder().path("d2.js").push();
    request.newPushBuilder().path("d3.js").push();
    request.newPushBuilder().path("logo_256_black.gif").push();
    request.newPushBuilder().path("logo_256_blue.gif").push();
    request.newPushBuilder().path("logo_256_gray.gif").push();
    request.newPushBuilder().path("logo_256_green.gif").push();         
    request.newPushBuilder().path("logo_256_red.gif").push();
    request.newPushBuilder().path("logo_256_yellow.gif").push();
}
%>
<html>
<head>
<link rel="stylesheet" type="text/css" href="general.css">
<script src="d1.js" type="text/javascript"></script>
<script src="d2.js" type="text/javascript"></script>
<script src="d3.js" type="text/javascript"></script>
<script type="text/javascript">
window.onload = function(e) { 
    setd1();
    setd2();
    setd3();
}
</script>
<title>Demo</title>
</head>
<body>
<h1>Demo</h1>
<h2>Static content:</h2>
<p>
It works!
</p>
<h2>Dynamic content:</h2>
<div id="d1"></div>
<div id="d2"></div>
<div id="d3"></div>
<h2>Graphics:</h2>
<img src="images/logo_256_black.gif" alt="Black logo">
<img src="images/logo_256_blue.gif" alt="Blue logo">
<img src="images/logo_256_gray.gif" alt="Gray logo">
<img src="images/logo_256_green.gif" alt="Green logo">
<img src="images/logo_256_red.gif" alt="Red logo">
<img src="images/logo_256_yellow.gif" alt="Yellow logo">
</body>
</html>

Loading this JSP page result in only one HTTP/2.0 request.

This sound very cool right, so everybody should always do this? No - it is actually a bit more complicated and that!

The benefit of server push and browser caching are inverse correlated. Browsers cache content to improve performance and then it is not necessary to send it again.

Let us take the previous example and assume bar.jpg is in the browser cache.

HTTP 1.1 (and HTTP 2.0 without server push) work like:

  1. browser request foo.html
  2. server send foo.html part 1
  3. server send foo.html part 2
  4. server send foo.html part 3
  5. browser parse foo.html and determines that it needs bar.jpg but note that it already has it in cache

HTTP 2.0 with server push work like:

  1. browser request foo.html
  2. server send "you need bar.jpg and I will send it in stream 2"
  3. browse reset stream 2
  4. server send foo.html part 1 (in stream 1)
  5. server send foo.html part 2 (in stream 1)
  6. server send foo.html part 3 (in stream 1)
  7. browser parse foo.html and determines that it needs bar.jpg but note that it already has it in cache

In this case server push can decrease performance.

So my recommendation is:

Note that I am not an expert in web site performance, so for any serious web site optimization then you should probably consult someone that actually is an expert.

Note that even though all modern browsers support HTTP 2.0, then they typical only do so over HTTPS.

By convention it is actually called HTTP/2 and not HTTP 2.0, but I am a bit conservative regarding version numbers.

Jakarta EE 8

Jakarta EE 8 was released September 10th 2019.

There are no functional changes compared to Java EE 8 - it is a pure name change to reflect that responsibility has moved from Oracle to Eclipse.

Jakarta EE 9

Jakarta EE 9 was released November 22nd 2020.

There are no real functional changes - just a rename of all packages from javax.* to jakarta.*.

Jakarta EE 9.1

Jakarta EE 9.1 was released May 25th 2021.

There are no real functional changes - just add support for Java 11 keeping support for Java 8.

Jakarta EE 10

Jakarta EE 10 was released September 22nd 2022.

This is the first Jakarta EE release with real functional changes.

It supports Java 11 and 17.

Faces (JSF) 4.0:

Several obsolete features has been removed.

The support for JSP as view technology has been removed.

This is about time. JSP view support has been very poor for quite some time by now.

The support for @ManagedBean has been removed and @Named is now the only available option.

Faces 4.0 supports extensionless URL's. This is enabled via WEB-INF/web.xml:

<context-param>
    <param-name>jakarta.faces.AUTOMATIC_EXTENSIONLESS_MAPPING</param-name>
    <param-value>true</param-value>
</context-param>

Faces 4.0 supports pure Java views.

Servlet 6.0:

Some obsolete features including SingleThreadModel and HttpSessionContext has been removed.

Servlet 6.0 has been aligned with CDI.

Servlet 6.0 supports Java modules.

Java Persistence (JPA) 3.1:

Jakarta Persistence (JPA) 3.1 supports new PQL and criteria API functions including a bunch of mathematical and date functions.

EntityManagerFactory and EntityManager are now AutoCloseable (support for try with resource).

Concurrency 3.0:

Jakarta Concurrency 3.0 introduces som new features including ManagedExecutorService and @Asynchronous with specific executor.

Here is a simple example showing how it works.

AsyncTest.java (demo servlet):

package test;

import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import jakarta.enterprise.concurrent.ContextServiceDefinition;
import jakarta.enterprise.concurrent.ManagedExecutorDefinition;
import jakarta.inject.Inject;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import static jakarta.enterprise.concurrent.ContextServiceDefinition.APPLICATION;
import static jakarta.enterprise.concurrent.ContextServiceDefinition.SECURITY;

@ContextServiceDefinition(name = "java:module/concurrent/MyContext",
                          propagated = {SECURITY, APPLICATION})
@ManagedExecutorDefinition(name = "java:module/concurrent/Executor100",
                           context = "java:module/concurrent/MyContext",
                           maxAsync = 100)
@ManagedExecutorDefinition(name = "java:module/concurrent/Executor2",
                           context = "java:module/concurrent/MyContext",
                           maxAsync = 2)
@WebServlet(urlPatterns={"/asynctest"})
public class AsyncTest extends HttpServlet {
    @Inject
    private Supp o;
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        long t1bef = System.currentTimeMillis();
        for(int i = 0; i < 10; i++) {
            o.m1();
        }
        long t1aft = System.currentTimeMillis();
        resp.getWriter().println(t1aft - t1bef);
        long t2bef = System.currentTimeMillis();
        for(int i = 0; i < 10; i++) {
            o.m2();
        }
        long t2aft = System.currentTimeMillis();
        resp.getWriter().println(t2aft - t2bef);
        CompletableFuture<Void>[] task = new CompletableFuture[10];
        long t3bef = System.currentTimeMillis();
        for(int i = 0; i < 10; i++) {
            task[i] = o.m3();
        }
        long t3aft = System.currentTimeMillis();
        for(int i = 0; i < 10; i++) {
            try {
                task[i].get();
            } catch(ExecutionException ex) {
                // nothing
            } catch(InterruptedException ex) {
                // nothing
            }
        }
        long t3done = System.currentTimeMillis();
        resp.getWriter().println((t3aft - t3bef) + " " + (t3done - t3bef));
        long t4bef = System.currentTimeMillis();
        for(int i = 0; i < 10; i++) {
            task[i] = o.m4();
        }
        long t4aft = System.currentTimeMillis();
        for(int i = 0; i < 10; i++) {
            try {
                task[i].get();
            } catch(ExecutionException ex) {
                // nothing
            } catch(InterruptedException ex) {
                // nothing
            }
        }
        long t4done = System.currentTimeMillis();
        resp.getWriter().println((t4aft - t4bef) + " " + (t4done - t4bef));
        long t5bef = System.currentTimeMillis();
        for(int i = 0; i < 10; i++) {
            task[i] = o.m5();
        }
        long t5aft = System.currentTimeMillis();
        for(int i = 0; i < 10; i++) {
            try {
                task[i].get();
            } catch(ExecutionException ex) {
                // nothing
            } catch(InterruptedException ex) {
                // nothing
            }
        }
        long t5done = System.currentTimeMillis();
        resp.getWriter().println((t5aft - t5bef) + " " + (t5done - t5bef));
    }
}

Supp.java (demo async methods):

package test;

import java.util.concurrent.CompletableFuture;

import jakarta.enterprise.concurrent.Asynchronous;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class Supp {
    public void m1() {
        try {
            Thread.sleep(100);
        } catch(InterruptedException ex) {
            // nothing
        }
    }
    @Asynchronous
    public void m2() {
        try {
            Thread.sleep(100);
        } catch(InterruptedException ex) {
            // nothing
        }
    }
    @Asynchronous
    public CompletableFuture<Void> m3() {
        try {
            Thread.sleep(100);
        } catch(InterruptedException ex) {
            // nothing
        }
        return Asynchronous.Result.complete(null);
    }
    @Asynchronous(executor = "java:module/concurrent/Executor100")
    public CompletableFuture<Void> m4() {
        try {
            Thread.sleep(100);
        } catch(InterruptedException ex) {
            // nothing
        }
        return Asynchronous.Result.complete(null);
    }
    @Asynchronous(executor = "java:module/concurrent/Executor2")
    public CompletableFuture<Void> m5() {
        try {
            Thread.sleep(100);
        } catch(InterruptedException ex) {
            // nothing
        }
        return Asynchronous.Result.complete(null);
    }
}

Output:

1000
0
0 200
0 100
0 500

Which is as expected:

Article history:

Version Date Description
1.0 October 15th 2017 Initial version
1.1 December 29th 2020 Add note on Jakarta EE 8 and 9
1.2 October 16th 2022 Add Jakarta EE 10 section

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj