diff --git a/extensions/jsonb/bnd.bnd b/extensions/jsonb/bnd.bnd
new file mode 100644
index 000000000..fd7afa7ab
--- /dev/null
+++ b/extensions/jsonb/bnd.bnd
@@ -0,0 +1 @@
+Fragment-Host: io.jsonwebtoken.jjwt-api
diff --git a/extensions/jsonb/pom.xml b/extensions/jsonb/pom.xml
new file mode 100644
index 000000000..ee2f1e176
--- /dev/null
+++ b/extensions/jsonb/pom.xml
@@ -0,0 +1,47 @@
+
+
+ 4.0.0
+
+
+ io.jsonwebtoken
+ jjwt-root
+ 0.11.3-SNAPSHOT
+ ../../pom.xml
+
+
+ jjwt-jsonb
+ JJWT :: Extensions :: JSON-B
+ jar
+
+
+ ${basedir}/../..
+
+ 8
+
+
+
+
+ io.jsonwebtoken
+ jjwt-api
+
+
+ jakarta.json
+ jakarta.json-api
+ test
+
+
+ jakarta.json.bind
+ jakarta.json.bind-api
+ provided
+
+
+
+
+ org.apache.johnzon
+ johnzon-jsonb
+ test
+
+
+
diff --git a/extensions/jsonb/src/main/java/io/jsonwebtoken/jsonb/io/JsonbDeserializer.java b/extensions/jsonb/src/main/java/io/jsonwebtoken/jsonb/io/JsonbDeserializer.java
new file mode 100644
index 000000000..53bfabb70
--- /dev/null
+++ b/extensions/jsonb/src/main/java/io/jsonwebtoken/jsonb/io/JsonbDeserializer.java
@@ -0,0 +1,66 @@
+/*
+ * 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 0.10.0
+ */
+public class JsonbDeserializer implements Deserializer {
+
+ private final Class 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) Object.class);
+ }
+
+ private JsonbDeserializer(Jsonb jsonb, Class 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);
+ }
+
+}
diff --git a/extensions/jsonb/src/main/java/io/jsonwebtoken/jsonb/io/JsonbSerializer.java b/extensions/jsonb/src/main/java/io/jsonwebtoken/jsonb/io/JsonbSerializer.java
new file mode 100644
index 000000000..25b949ada
--- /dev/null
+++ b/extensions/jsonb/src/main/java/io/jsonwebtoken/jsonb/io/JsonbSerializer.java
@@ -0,0 +1,75 @@
+/*
+ * 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 0.10.0
+ */
+public class JsonbSerializer implements Serializer {
+
+ 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 {
+ Assert.notNull(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);
+ }
+}
diff --git a/extensions/jsonb/src/main/resources/META-INF/services/io.jsonwebtoken.io.Deserializer b/extensions/jsonb/src/main/resources/META-INF/services/io.jsonwebtoken.io.Deserializer
new file mode 100644
index 000000000..40ea24d55
--- /dev/null
+++ b/extensions/jsonb/src/main/resources/META-INF/services/io.jsonwebtoken.io.Deserializer
@@ -0,0 +1 @@
+io.jsonwebtoken.jsonb.io.JsonbDeserializer
diff --git a/extensions/jsonb/src/main/resources/META-INF/services/io.jsonwebtoken.io.Serializer b/extensions/jsonb/src/main/resources/META-INF/services/io.jsonwebtoken.io.Serializer
new file mode 100644
index 000000000..100cf3444
--- /dev/null
+++ b/extensions/jsonb/src/main/resources/META-INF/services/io.jsonwebtoken.io.Serializer
@@ -0,0 +1 @@
+io.jsonwebtoken.jsonb.io.JsonbSerializer
diff --git a/extensions/jsonb/src/test/groovy/io/jsonwebtoken/jsonb/io/JsonbDeserializerTest.groovy b/extensions/jsonb/src/test/groovy/io/jsonwebtoken/jsonb/io/JsonbDeserializerTest.groovy
new file mode 100644
index 000000000..88386e8f7
--- /dev/null
+++ b/extensions/jsonb/src/test/groovy/io/jsonwebtoken/jsonb/io/JsonbDeserializerTest.groovy
@@ -0,0 +1,76 @@
+package io.jsonwebtoken.jsonb.io
+
+import io.jsonwebtoken.io.DeserializationException
+import io.jsonwebtoken.io.Deserializer
+import io.jsonwebtoken.lang.Strings
+import org.junit.Test
+
+import javax.json.bind.JsonbBuilder
+
+import static org.easymock.EasyMock.*
+import static org.hamcrest.CoreMatchers.instanceOf
+import static org.hamcrest.MatcherAssert.assertThat
+import static org.junit.Assert.*
+
+class JsonbDeserializerTest {
+
+ @Test
+ void loadService() {
+ def deserializer = ServiceLoader.load(Deserializer).iterator().next()
+ assertThat(deserializer, instanceOf(JsonbDeserializer))
+ }
+
+
+ @Test
+ void testDefaultConstructor() {
+ def deserializer = new JsonbDeserializer()
+ assertNotNull deserializer.jsonb
+ }
+
+ @Test
+ void testObjectMapperConstructor() {
+ def customJsonb = JsonbBuilder.create()
+ def deserializer = new JsonbDeserializer(customJsonb)
+ assertSame customJsonb, deserializer.jsonb
+ }
+
+ @Test(expected = NullPointerException)
+ void testObjectMapperConstructorWithNullArgument() {
+ new JsonbDeserializer<>(null)
+ }
+
+ @Test
+ void testDeserialize() {
+ byte[] serialized = '{"hello":"世界"}'.getBytes(Strings.UTF_8)
+ def expected = [hello: '世界']
+ def result = new JsonbDeserializer().deserialize(serialized)
+ assertEquals expected, result
+ }
+
+ @Test
+ void testDeserializeFailsWithJsonProcessingException() {
+
+ def ex = createMock javax.json.bind.JsonbException
+
+ expect(ex.getMessage()).andReturn('foo')
+
+ def deserializer = new JsonbDeserializer() {
+ @Override
+ protected Object readValue(byte[] bytes) throws javax.json.bind.JsonbException {
+ throw ex
+ }
+ }
+
+ replay ex
+
+ try {
+ deserializer.deserialize('{"hello":"世界"}'.getBytes(Strings.UTF_8))
+ fail()
+ } catch (DeserializationException se) {
+ assertEquals 'Unable to deserialize bytes into a java.lang.Object instance: foo', se.getMessage()
+ assertSame ex, se.getCause()
+ }
+
+ verify ex
+ }
+}
diff --git a/extensions/jsonb/src/test/groovy/io/jsonwebtoken/jsonb/io/JsonbSerializerTest.groovy b/extensions/jsonb/src/test/groovy/io/jsonwebtoken/jsonb/io/JsonbSerializerTest.groovy
new file mode 100644
index 000000000..da8d3c096
--- /dev/null
+++ b/extensions/jsonb/src/test/groovy/io/jsonwebtoken/jsonb/io/JsonbSerializerTest.groovy
@@ -0,0 +1,111 @@
+package io.jsonwebtoken.jsonb.io
+
+import io.jsonwebtoken.io.SerializationException
+import io.jsonwebtoken.io.Serializer
+import io.jsonwebtoken.lang.Strings
+import org.junit.Test
+
+import javax.json.bind.JsonbBuilder
+import javax.json.bind.JsonbException
+
+import static org.easymock.EasyMock.*
+import static org.hamcrest.CoreMatchers.instanceOf
+import static org.hamcrest.MatcherAssert.assertThat
+import static org.junit.Assert.*
+
+class JsonbSerializerTest {
+
+ @Test
+ void loadService() {
+ def serializer = ServiceLoader.load(Serializer).iterator().next()
+ assertThat(serializer, instanceOf(JsonbSerializer))
+ }
+
+ @Test
+ void testDefaultConstructor() {
+ def serializer = new JsonbSerializer()
+ assertNotNull serializer.jsonb
+ }
+
+ @Test
+ void testObjectMapperConstructor() {
+ def customJsonb = JsonbBuilder.create()
+ def serializer = new JsonbSerializer<>(customJsonb)
+ assertSame customJsonb, serializer.jsonb
+ }
+
+ @Test(expected = NullPointerException)
+ void testObjectMapperConstructorWithNullArgument() {
+ new JsonbSerializer<>(null)
+ }
+
+ @Test
+ void testByte() {
+ byte[] expected = "120".getBytes(Strings.UTF_8) //ascii("x") = 120
+ byte[] bytes = "x".getBytes(Strings.UTF_8)
+ byte[] result = new JsonbSerializer().serialize(bytes[0]) //single byte
+ assertTrue Arrays.equals(expected, result)
+ }
+
+ @Test
+ void testByteArray() { //expect Base64 string by default:
+ byte[] bytes = "hi".getBytes(Strings.UTF_8)
+ String expected = '"aGk="' as String //base64(hi) --> aGk=
+ byte[] result = new JsonbSerializer().serialize(bytes)
+ assertEquals expected, new String(result, Strings.UTF_8)
+ }
+
+ @Test
+ void testEmptyByteArray() { //expect Base64 string by default:
+ byte[] bytes = new byte[0]
+ byte[] result = new JsonbSerializer().serialize(bytes)
+ assertEquals '""', new String(result, Strings.UTF_8)
+ }
+
+ @Test
+ void testChar() { //expect Base64 string by default:
+ byte[] result = new JsonbSerializer().serialize('h' as char)
+ assertEquals "\"h\"", new String(result, Strings.UTF_8)
+ }
+
+ @Test
+ void testCharArray() { //expect Base64 string by default:
+ byte[] result = new JsonbSerializer().serialize("hi".toCharArray())
+ assertEquals "\"hi\"", new String(result, Strings.UTF_8)
+ }
+
+ @Test
+ void testSerialize() {
+ byte[] expected = '{"hello":"世界"}'.getBytes(Strings.UTF_8)
+ byte[] result = new JsonbSerializer().serialize([hello: '世界'])
+ assertTrue Arrays.equals(expected, result)
+ }
+
+
+ @Test
+ void testSerializeFailsWithJsonProcessingException() {
+
+ def ex = createMock(JsonbException)
+
+ expect(ex.getMessage()).andReturn('foo')
+
+ def serializer = new JsonbSerializer() {
+ @Override
+ protected byte[] writeValueAsBytes(Object o) throws JsonbException {
+ throw ex
+ }
+ }
+
+ replay ex
+
+ try {
+ serializer.serialize([hello: 'world'])
+ fail()
+ } catch (SerializationException se) {
+ assertEquals 'Unable to serialize object: foo', se.getMessage()
+ assertSame ex, se.getCause()
+ }
+
+ verify ex
+ }
+}
diff --git a/extensions/pom.xml b/extensions/pom.xml
index ee13b0f20..52cc61316 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -37,5 +37,6 @@
jackson
orgjson
gson
+ jsonb
-
\ No newline at end of file
+
diff --git a/pom.xml b/pom.xml
index fe2bea294..89b961166 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,6 +77,9 @@
2.12.6
20180130
2.8.9
+ 1.1.6
+ 1.0.2
+ 1.2.16
1.67
@@ -138,6 +141,24 @@
gson
${gson.version}
+
+ jakarta.json
+ jakarta.json-api
+ ${jsonp.version}
+ test
+
+
+ jakarta.json.bind
+ jakarta.json.bind-api
+ ${jsonb.version}
+ provided
+
+
+ org.apache.johnzon
+ johnzon-jsonb
+ ${johnzon.version}
+ test
+