Introducing JSON-B with Spring Boot 2.0

JSON Binding (JSON-B) is the new Java EE specification for converting JSON messages to Java Objects and back. JSON is used everywhere and so far we had two main ways of dealing with JSON conversion in Java- using either Jackson or GSON. With the introduction of JSON-B, we have a standard way of handling this conversion. In this article, we will see how Spring Boot 2.0 supports JSON-B, how easy it is to use it and how does it compare with the other options.

If you want to know more about JSON-B itself, you can visit the official website or read the four parts Getting started with the JSON API by IBM. I found the fourth part comparing JSON-B, Jackson, and GSON very interesting. Now it is time to see JSON-B in action!

Converting Java Objects to JSON with Spring Boot 2.0 and JSON-B

How do you get JSON-B to work with Spring Boot 2.0? It is very simple. You need to add the required Maven dependencies:

<dependency>
	<groupId>javax.json.bind</groupId>
	<artifactId>javax.json.bind-api</artifactId>
	<version>1.0</version>
</dependency>
<dependency>
	<groupId>org.eclipse</groupId>
	<artifactId>yasson</artifactId>
	<version>1.0</version>
</dependency>
<dependency>
	<groupId>org.glassfish</groupId>
	<artifactId>javax.json</artifactId>
	<version>1.1</version>
</dependency>

And you need to choose the preffered-json-mapper setting to make sure that JSON-B is chosen. You may get GSON or Jackson on the classpath and then you can’t be sure how the autoconfiguration will work without this setting:

spring.http.converters.preferred-json-mapper=jsonb

With that in place, we are going to write a simple Rest Controller and a simple Car Class, that will make use of the JSON-B conversion:

package com.e4developer.jsonbexample;

import org.springframework.web.bind.annotation.*;

import java.util.Calendar;
import java.util.Optional;

@RestController
public class SimpleController {

    private Car makeCar() {
        Car newCar = new Car();
        newCar.make = "e4Cars";
        newCar.model = "theSensible";
        newCar.bonusFeatures = Optional.empty();
        newCar.price = 6000;
        newCar.productionDate = new Calendar.Builder().setDate(2018, 3, 3).build();
        return newCar;
    }

    @GetMapping("/car")
    public Car car() {
        Car newCar = makeCar();
        return newCar;
    }
}
package com.e4developer.jsonbexample;

import java.util.Calendar;
import java.util.Objects;
import java.util.Optional;

public class Car {
    public String make;
    public String model;
    public int price;
    public Calendar productionDate;
    public Optional<String> bonusFeatures;

    @Override
    public String toString() {
        return "Car{" +
                "make='" + make + '\'' +
                ", model='" + model + '\'' +
                ", price=" + price +
                ", productionDate=" + productionDate +
                ", bonusFeatures=" + bonusFeatures +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o;
        return price == car.price &&
                Objects.equals(make, car.make) &&
                Objects.equals(model, car.model) &&
                Objects.equals(productionDate, car.productionDate) &&
                Objects.equals(bonusFeatures, car.bonusFeatures);
    }

    @Override
    public int hashCode() {
        return Objects.hash(make, model, price, productionDate, bonusFeatures);
    }
}

Now let’s see how the response from that endpoint looks like:

{
    "make": "e4Cars",
    "model": "theSensible",
    "price": 6000,
    "productionDate": "2018-04-03T00:00:00+01:00[Europe/London]"
}

It is worth noting a few things. Treatment of the Calendar is specific to JSON-B specification and so is the simple disappearing of the missing Optional. I think the treatment of these two concepts is quite clean here.

The equivalent response with GSON would be:

{
    "make": "e4Cars",
    "model": "theSensible",
    "price": 6000,
    "productionDate": {
        "year": 2018,
        "month": 3,
        "dayOfMonth": 3,
        "hourOfDay": 0,
        "minute": 0,
        "second": 0
    },
    "bonusFeatures": {}
}

Note the different treatment of the Calendar and the quite peculiar thing that happens to Optional. It is not quite null, but not quite an Object either.

And with Jackson:

{
    "make": "e4Cars",
    "model": "theSensible",
    "price": 6000,
    "productionDate": "2018-04-02T23:00:00.000+0000",
    "bonusFeatures": null
}

Again, the Calendar is treated differently and the treatment of Optional at least can be seen as sensible- it is clearly a null.

If you are interested in a more detailed comparison between JSON-B, GSON, and Jackson standards, I once again recommend the IBM article dealing with the subject.

I showed you these three different examples to really make a point here. There is a lot of value from an official standard that deals with converting Java to JSON. Of course, JavaEE championed standards were not always successful. The original EJBs were mostly a disaster (sorry if you liked them, but sadly they did not catch on). CDI beans were much better (although they did not get enough traction in my opinion). JPA was on the other hand very successful and it is still very popular.

I strongly believe that the JSR 367 (this is Java Specification Requests for JSON-B) is here to stay and will bring a lot of good to the JVM ecosystem. I think we all want a more united and more seamless JSON development experience in the Java world.

We will see now how these different libraries deal with JSON to Java conversion.

Converting JSON to Java Objects

It is important for a converter to be able to convert both ways between Java Object and JSON without changing the object itself. With the new standard, I wanted to see how well it handles this task.

We will use the original Car Object, get the JSON from JSON-B, GSON, and Jackson and then feed it back with a POST to see how the recreated Object looks like. We will check the equality to the Original Object and inspect any potential changes in the payload when returned once again.

For that I wrote a simple Controller endpoint:

    @PostMapping("/sendCar")
    public Car sendCar(@RequestBody Car car){
        System.out.println("original car: "+makeCar());
        System.out.println("transformed car: "+car.toString());
        System.out.println("Is the car the same? "+ car.equals(makeCar()));
        return car;
    }
Conversion with JSON-B

JSON-B passes the simple test of returning the same JSON text that it is sent.

JSON-B fails the equality check! It turns out that JSON-B assigns a naked null rather than the Optional value to the field when it is not present in the JSON! This is very disappointing, as it may cause unexpected bugs. To be clear what happens is:

  1. JSON-B deals with Optional.empty() by not including it in the JSON response.
  2. When JSON parsed into Java Object does not have a value for an Optional<> field it assings it null rather than Optional.empty()
  3. This breaks the idea of Optional

This is disappointing enough that I hope it will change in the future iterations of the standard.

Conversion with GSON

GSON passes the simple test of returning the same JSON text that it is sent.

GSON also passes the equality check! It correctly deals with the Optional and the Calendar cases.

Conversion with JACKSON

Jackson passes the simple test of returning the same JSON text that it is sent.

Jackson fails the equality check! While Jackson is correctly handling the Optional case it fails to deal with the Calendar correctly. It loses the Zone information (in my case London/Europe) and changes it to UTC. This simplification can cause unexpected bugs in different systems.

Conclusion

I believe that JSON-B is a great idea for a new standard. JSON became so important that having Java community agree on a good way of dealing with it can be very helpful.

It is apparent that dealing with JSON to Java and back is not as trivial as it may seem. As demonstrated here, these conversions may end up causing some unexpected side effects. Personally, I would like JSON-B to be more like GSON- perhaps sacrifice human readability for a clear back to back conversion.

No matter which converter you chose, Spring Boot 2.0 makes it easy to use. I am looking forward to JSON-B entering the field as the third worthy contender for your JSON converter of choice.

The code used for this article is available on my Github account.

5 thoughts on “Introducing JSON-B with Spring Boot 2.0”

  1. Hey! Interesting article. 🙂

    I thought that `Optional`s weren’t meant to be attributes/parameters but only return values in order to not deal with `null`s. I’d love to know why they are not properly unserialised though…

    1. Hey antoinm!

      Even if they are not meant to be attributes parameter by design, they can still be used in that fashion and JSON libraries need some way of dealing with that. Good point though- if you don’t use it, you are not exposed to the risk.

      I think the JSON to Java part fails to deal with Optional here, as it is not using a constructor and only manually mapping attributes (not being too smart). With this approach, it seems that Jackson and GSON way of dealing with it is simpler.

  2. Nice examples. A few comments, though:

    1) JSON-B is not “the new Java EE standard” — it is simply a specification that is included in Java EE. Java EE is a collection of specifications implemented as a platform. Well, saying that JSON-B is ” the new Java EE standard” is not wrong in itself but in this context (this tutorial is about Spring Boot), the distinction between Java EE and Spring is not clear. I would rephrase it, you can’t imagine how high is the general Java EE/Spring confusion.

    Just say that “JSON-B is the new Java API for JSON Binding (JSR-367)” and in the end of the article, if you wish, you can specify: “if you are using JavaEE instead of Spring, JSON-B is supported by default in Java EE 8”.

    2) “JSON is used everywhere and so far we had two main ways of dealing with JSON conversion in Java- using either Jackson or GSON.” — this phrase is not entirely correct. JSON-B is a new specification, indeed, for *binding* objects. But we had JSON-P (you know, javax.json.JsonObject) since a long time ago.

    3) Why are you including as dependency the following:

    org.glassfish
    javax.json
    1.1

    That is the implementation of JSON-P, yet you did not include the JSON-P api. Is it needed somehow for Yasson to work? Does Yasson include the JSON-P api? I am not sure. Either way, you should clarify this JSON-B/JSON-P topic.

    Finally, my personal opinion about JSON-B and Yasson is that they are not improving anything. They are simply encouraging get/set POJOs (model objects), which is the worst imaginable thing for OOP. I wrote about it here: http://www.amihaiemil.com/2017/07/04/yasson-yet-another-POJO-parser.html

    1. Thanks for the detailed comment!

      1) I edited the sentence to say specification rather than standard. I think it adds clarity.
      2) I see where you are coming from. Maybe I could improve the phrasing to be more precise here.
      3) The official JSON-B page claims that you need that dependency. I could see if it works without, but I just followed their documentation here. Could be not needed though.

      Interesting blog post about POJOs! I personally think POJOs have their place (interacting with some 3rd party APIs), but I agree that anaemic data models are often the wrong way to build your system.

      Thanks for the feedback!

Comments are closed.