Java EE Update

Content:

  1. Introduction
  2. Java EE 8
    1. New JSON API's
    2. HTTP 2.0 support

Introduction:

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

Java EE 8:

Java EE 8 was released September 21st 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="logo_256_black.gif" alt="Black logo">
<img src="logo_256_blue.gif" alt="Blue logo">
<img src="logo_256_gray.gif" alt="Gray logo">
<img src="logo_256_green.gif" alt="Green logo">
<img src="logo_256_red.gif" alt="Red logo">
<img src="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="logo_256_black.gif" alt="Black logo">
<img src="logo_256_blue.gif" alt="Blue logo">
<img src="logo_256_gray.gif" alt="Gray logo">
<img src="logo_256_green.gif" alt="Green logo">
<img src="logo_256_red.gif" alt="Red logo">
<img src="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.

Article history:

Version Date Description
1.0 October 15th 2017 Initial version

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj