JSON-P and JSON-B

JavaScript Object Notation (JSON) is the go-to format for sharing data in microservices-based systems. Jakarta EE provides two API specifications for converting plain Java objects (POJOs) to and from JSON data: JSON processing (JSON-P) and JSON binding (JSON-B).

JSON supports two standard data structures: objects and arrays. Objects are sets of key-value pairs that are usually surrounded by curly brackets. Arrays collect objects into larger sets. These objects and arrays are serialized and deserialized for transfer between different microservices. JSON-P provides a Java API for processing JSON-formatted data. JSON-B provides a binding layer on top of JSON-P, which further simplifies the conversion of objects to and from JSON.

In Open Liberty, you can use the JSON APIs by enabling either the Jakarta JSON Processing feature, for JSON-P, or the Jakarta JSON Binding feature, for JSON-B.

JSON-P: the original Java EE JSON API

Before the release of JSON-B as part of Java EE 8, JSON-P was the standardized way for Java EE to interact with JSON. JSON-P is a lower-level API that provides two models, streaming and object, for JSON processing and transformation. The streaming model provides an event-based parser that allows a developer to ask for the next event rather than handling the event in a callback, which enables more control over the processing. The object model creates a random access structure to represent the JSON data in memory. This programming model is more flexible but sometimes requires more memory than the streaming model.

In contrast to JSON-B, JSON-P avoids data loss by retaining all the information in a call. This approach can be either an advantage or a liability, depending on what the code is doing.

JSON-B: a higher-level API

JSON-B is a standard binding layer for converting Java objects to and from JSON data. It provides default mapping to convert existing Java classes to JSON, but also enables developers to customize mapping by using Java annotations. JSON-B is a direct object-to-JSON mapping and requires less code than JSON-P. The intuitive default configurations for JSON B make it easy to use. Even developers with no prior experience with JSON can use JSON-B because it is based on annotations and semantics that are already commonplace for Java developers.

JSON-P and JSON-B use cases

JSON-B is the preferred API for converting Java objects to and from JSON, thanks to its type safety, ease of use, and compile-time feedback. However, in some cases, JSON-P might be a better fit.

For example, JSON-P might be preferred if data loss is a concern. In the following example, JSON data that is being sent between microservices represents a user.

{
  "id" : 5678,
  "name" : "Hank",
  "age": 55,
  "roles" : [ "member" ]
}

If a service that uses JSON-B received this object, but only asked for the id and name values, the other information would be lost. A service that uses JSON-P would retain all the data in the object.

JSON-P is also useful in rare cases where an application needs to parse an arbitrary set of JSON data and the schema is not known ahead of time. With JSON-B, the developer must know the schema of the JSON in advance.

Basic use of JSON-B

The following example shows a data class.

public static class User {

    // Public fields are included in the JSON data by default
    public long id;
    public Set<String> roles;
    public String name;
    public int age;

    // Private fields are ignored by JSON-B by default
    private String somethingSecret = "hello";

    // Static fields are also ignored by JSON-B by default
    public static String FOO_CONSTANT = "FOO";

    public User() {
        // A default constructor is required
        // If no default constructor is present, the class must be annotated with @JsonbCreator
    }

    public User(long id, String name, int age, Set<String> roles) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.roles = roles;
    }
}

If a default constructor is present, the data class does not need any annotations or any reference to the JSON-B API.

The next code example shows how the default mappings of JSON-B marshal and unmarshal a data object. The main JSON-B entry point is the jakarta.json.bind.Jsonb class. It provides all the necessary methods to serialize and deserialize Java objects. Instances of the Jsonb interface are thread-safe, which means they can be cached and reused whenever possible.

// Cache Jsonb objects whenever possible. They are relatively expensive to create
private static final Jsonb jsonb = JsonbBuilder.create();
// ...

// Convert POJO --> JSON
User andy = new User(1234, "Andy", 77, Collections.singleton("admin"));
String andyJson = jsonb.toJson(andy);
System.out.println(andyJson); // prints: {"age":77,"id":1234,"name":"Andy","roles":["admin"]}

// Convert JSON --> POJO
String jsonData = "{\"age\":55,\"id\":5678,\"name\":\"Hank\",\"roles\":[\"member\"]}";
User hank = jsonb.fromJson(jsonData, User.class);

Customized mappings between Java and JSON

By default, JSON property names match Java field or method names. If matching names are not wanted or not possible, you can define new mappings, as shown in the following example.

// maps the 'name' field to the 'fullName' JSON property
@JsonbProperty("fullName")
public String name;

// even though the field is public, tell JSON-B to ignore this field so it is not included in the JSON output
@JsonbTransient
public Set<String> roles;

JSON-B and JAX-RS

JSON-B works well with JAX-RS because JAX-RS resources often consume or produce JSON data over HTTP, but within endpoints that interact with the data as POJOs. The following code example is a simple JAX-RS endpoint that sends and receives information by using JSON. However, the implementation of the endpoint works with POJOs, without needing to explicitly convert to and from JSON data.

@Path("/user")
@ApplicationScoped
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class UserService {

    @Inject
    UserDB db;

    @GET
    @Path("/{userId}")
    public User getUserById(@PathParam("userId") String id) {
        User u = db.get(id);
        return u;
        // returned User object will be converted to JSON data using JSON-B
    }

    @POST
    @Path("/{userId}")
    public String createUser(User updatedUser, @PathParam("userId")) {
        // The incoming 'updatedUser' parameter gets read from
        // JSON data in the incoming request body and automatically converted to a User object
        updatedUser.id = // generate an ID
        db.save(updatedUser);
        return updatedUser.id;
    }
}

Third-party JSON-P and JSON-B providers

In Open Liberty, you can use the default reference implementations of JSON-P and JSON-B by using the Jakarta JSON Processing feature, for JSON-P, or the Jakarta JSON Binding feature, for JSON-B.

If you prefer to use a third-party implementation for JSON-P or JSON-B, use the Jakarta JSON Processing Container feature for JSON-P or the Jakarta JSON Binding Container feature, for JSON-B, instead. Then, configure a library and a Basic Extension using Liberty Libraries (BELL) that provides an implementation of the JSON-P or JSON-B API. You can specify a third-party implementation for either the JSON-P or JSON-B API by configuring a bell element in your server.xml file.

For example, Apache Johnzon is a third-party JSON-B implementation. In the following example, a server is configured to use Johnzon instead of the default reference implementation, which is Yasson.

<server>
  <featureManager>
    <feature>jsonbContainer-2.0</feature>
  </featureManager>

  <bell libraryRef="johnzon"/>

  <library id="johnzon">
    <fileset dir="${server.config.dir}/johnzon"includes="*.jar"/>
  </library>
</server>

You can also mix and match reference implementations and third-party implementations, if both implementations support the same Jakarta EE version.

For example, Apache Johnzon also provides a JSON-P implementation. In the following example, a server is configured to use Johnzon instead of the default reference implementation for JSON-P, which is GlassFish. However, the server continues to use Yasson for JSON-B.

<server>
  <featureManager>
    <feature>jsonb-2.0</feature>
    <feature>jsonpContainer-2.0</feature>
  </featureManager>

  <bell libraryRef="johnzon"/>

  <library id="johnzon">
    <fileset dir="${server.config.dir}/johnzon" includes="*.jar"/>
  </library>
</server>

Implementation precedence

Other features, such as MicroProfile, enable the jsonp and jsonb features by default. However, the jsonbContainer and jsonpContainer features supersede the jsonb and jsonp features. When you enable either the jsonbContainer or jsonpContainer feature, you can provide a third-party implementation, even if the jsonb or jsonp features are enabled by default.

If logging is enabled, the following information messages confirm this behavior.

CWWKJ0350I: The jsonb and jsonbContainer features are both enabled. The jsonbContainer feature supersedes the jsonb feature.
CWWKJ0351I: The jsonp and jsonpContainer features are both enabled. The jsonpContainer feature supersedes the jsonp feature.

However, you still must configure a bell element in your server.xml file that specifies the JSON API implementation that your application uses. If not, the following error occurs when your application attempts to use the JSON API.

CWWKJ0350I: jsonb and jsonbContainer features are both enabled. The jsonbContainer feature supersedes the jsonb feature.
jakarta.json.bind.JsonbException: JSON Binding provider org.eclipse.yasson.JsonBindingProvider not found