To main content

Using JJWT with the Jakarta JSON Binding API

Published by Benjamin Marwell on

As you may have seen in my earlier posts, JJWT is a sensible choice for a JSON Web Token (JWT, pronounced 'jot') Library. JJWT is separated into a few modules: The API, the implementation and the JSON serializer. You can currently choose between Jackson and GSON as a JSON serializer. But if you work in a Jakarta EE or MicroProfile environment, you might want to consider your own JSON-B provider instead.

What is JJWT?

JJWT (pronounced: jay-jay-doubleyou-tee, not jay-jot) is a library which is able to both create and parse JWTs in Java. It is compatible with Java 7 (because of Android 6 Marshmellow which needs to be supported) and lightweight. JJWT is used in many Open Source or corporate projects. While there is also the auth0/java-jwt library, it is not uncommon to see JJWT being used instead.

Why would I want to use my own JSON provider?

You will want to use your own JSON provider with JJWT, if:

  • … you are in a Jakarta EE or Microprofile Environment already

  • … don’t want to pull in a specific JSON implementation

  • … you are not using either Jackson, org.json or GSON

In this case, using an implementation-agnostic JSON-B provider allows you to switch between implementations at runtime. In other words, you do not need to reimplement the JSON serializer for running on another Application Server or when switching out libraries.

Implementing the JSON provider

You only need to implement two classes and add the service files.

These are the implementations which will not pull in a specific provider:

io.jsonwebtoken.jsonb.io.JsonbDeserializer.java
/*
 * Copyright (C) 2014 jsonwebtoken.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.jsonwebtoken.jsonb.io;

import io.jsonwebtoken.io.DeserializationException;
import io.jsonwebtoken.io.Deserializer;

import javax.json.bind.Jsonb;
import javax.json.bind.JsonbException;
import java.nio.charset.StandardCharsets;

import static java.util.Objects.requireNonNull;

/**
 * @since JJWT_RELEASE_VERSION
 */
public class JsonbDeserializer<T> implements Deserializer<T> {

    private final Class<T> returnType;
    private final Jsonb jsonb;

    @SuppressWarnings("unused") //used via reflection by RuntimeClasspathDeserializerLocator
    public JsonbDeserializer() {
        this(JsonbSerializer.DEFAULT_JSONB);
    }

    @SuppressWarnings({"unchecked", "WeakerAccess", "unused"}) // for end-users providing a custom ObjectMapper
    public JsonbDeserializer(Jsonb jsonb) {
        this(jsonb, (Class<T>) Object.class);
    }

    private JsonbDeserializer(Jsonb jsonb, Class<T> returnType) {
        requireNonNull(jsonb, "ObjectMapper cannot be null.");
        requireNonNull(returnType, "Return type cannot be null.");
        this.jsonb = jsonb;
        this.returnType = returnType;
    }

    @Override
    public T deserialize(byte[] bytes) throws DeserializationException {
        try {
            return readValue(bytes);
        } catch (JsonbException jsonbException) {
            String msg = "Unable to deserialize bytes into a " + returnType.getName() + " instance: " + jsonbException.getMessage();
            throw new DeserializationException(msg, jsonbException);
        }
    }

    protected T readValue(byte[] bytes) {
        return jsonb.fromJson(new String(bytes, StandardCharsets.UTF_8), returnType);
    }

}
io.jsonwebtoken.jsonb.io.JsonbSserializer.java
/*
 * Copyright (C) 2014 jsonwebtoken.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.jsonwebtoken.jsonb.io;

import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.io.SerializationException;
import io.jsonwebtoken.io.Serializer;
import io.jsonwebtoken.lang.Assert;

import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbException;
import java.nio.charset.StandardCharsets;

import static java.util.Objects.requireNonNull;

/**
 * @since JJWT_RELEASE_VERSION
 */
public class JsonbSerializer<T> implements Serializer<T> {

    static final Jsonb DEFAULT_JSONB = JsonbBuilder.create();

    private final Jsonb jsonb;

    @SuppressWarnings("unused") //used via reflection by RuntimeClasspathDeserializerLocator
    public JsonbSerializer() {
        this(DEFAULT_JSONB);
    }

    @SuppressWarnings("WeakerAccess") //intended for end-users to use when providing a custom ObjectMapper
    public JsonbSerializer(Jsonb jsonb) {
        requireNonNull(jsonb, "Jsonb cannot be null.");
        this.jsonb = jsonb;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        requireNonNull(t, "Object to serialize cannot be null.");
        try {
            return writeValueAsBytes(t);
        } catch (JsonbException jsonbException) {
            String msg = "Unable to serialize object: " + jsonbException.getMessage();
            throw new SerializationException(msg, jsonbException);
        }
    }

    @SuppressWarnings("WeakerAccess") //for testing
    protected byte[] writeValueAsBytes(T t) {
        final Object obj;

        if (t instanceof byte[]) {
            obj = Encoders.BASE64.encode((byte[]) t);
        } else if (t instanceof char[]) {
            obj = new String((char[]) t);
        } else {
            obj = t;
        }

        return this.jsonb.toJson(obj).getBytes(StandardCharsets.UTF_8);
    }
}

PR on GitHub

I am posting the code here, because my PR #720 still has not been merged. This will only get merged once Java 8 is the baseline, although the PR would work with the current Java 7 main branch. Only this single module would have been a Java 8 module, but Les (the maintainer) decided against it as the build would get slightly more complicated and modules requiring different versions of Java are confusing to users.

Alternatives

Instead of using the more abstract JSON-B provider, you could add every implementation:

Genson however does not implement JSON-B APIs, so you would need to write a module for this anyway.