diff --git a/docs/asciidoc/modules/modules.adoc b/docs/asciidoc/modules/modules.adoc
index 425bcf448f..5295fbe2a1 100644
--- a/docs/asciidoc/modules/modules.adoc
+++ b/docs/asciidoc/modules/modules.adoc
@@ -49,7 +49,7 @@ Modules are distributed as separate dependencies. Below is the catalog of offici
* link:{uiVersion}/modules/gson[Gson]: Gson module for Jooby.
* link:{uiVersion}/modules/jackson2[Jackson2]: Jackson2 module for Jooby.
* link:{uiVersion}/modules/jackson3[Jackson3]: Jackson3 module for Jooby.
- * link:{uiVersion}/modules/yasson[JSON-B]: JSON-B module for Jooby.
+ * link:{uiVersion}/modules/yasson[Yasson]: JSON-B module for Jooby.
==== OpenAPI
* link:{uiVersion}/modules/openapi[OpenAPI]: OpenAPI supports.
diff --git a/jooby/src/main/java/io/jooby/json/JsonCodec.java b/jooby/src/main/java/io/jooby/json/JsonCodec.java
new file mode 100644
index 0000000000..16ddf773ee
--- /dev/null
+++ b/jooby/src/main/java/io/jooby/json/JsonCodec.java
@@ -0,0 +1,31 @@
+/*
+ * Jooby https://jooby.io
+ * Apache License Version 2.0 https://jooby.io/LICENSE.txt
+ * Copyright 2014 Edgar Espina
+ */
+package io.jooby.json;
+
+/**
+ * A unified contract for complete JSON processing, combining both serialization and deserialization
+ * capabilities.
+ *
+ *
This interface acts as a convenient composite of {@link JsonEncoder} and {@link JsonDecoder}.
+ * Implementations of this interface (such as Jooby's Jackson, Gson, or Moshi integration modules)
+ * provide full-stack JSON support. This allows a Jooby application to seamlessly parse incoming
+ * JSON request bodies into Java objects, and render outgoing Java objects as JSON responses.
+ *
+ *
By providing a single interface that encompasses both directions of data binding, JSON
+ * libraries can be easily registered into the Jooby environment to handle all JSON-related content
+ * negotiation.
+ *
+ *
Important Note: Jooby core itself does not implement these
+ * interfaces. These contracts act as a bridge and are designed to be implemented exclusively by
+ * dedicated JSON modules (such as {@code jooby-jackson}, {@code jooby-gson}, or {@code
+ * jooby-avaje-json}), etc.
+ *
+ * @see JsonEncoder
+ * @see JsonDecoder
+ * @since 4.5.0
+ * @author edgar
+ */
+public interface JsonCodec extends JsonEncoder, JsonDecoder {}
diff --git a/jooby/src/main/java/io/jooby/json/JsonDecoder.java b/jooby/src/main/java/io/jooby/json/JsonDecoder.java
new file mode 100644
index 0000000000..4c5253adfb
--- /dev/null
+++ b/jooby/src/main/java/io/jooby/json/JsonDecoder.java
@@ -0,0 +1,74 @@
+/*
+ * Jooby https://jooby.io
+ * Apache License Version 2.0 https://jooby.io/LICENSE.txt
+ * Copyright 2014 Edgar Espina
+ */
+package io.jooby.json;
+
+import java.lang.reflect.Type;
+
+import io.jooby.Reified;
+
+/**
+ * Contract for decoding (deserializing) JSON strings into Java objects.
+ *
+ *
This functional interface provides the core deserialization strategy for Jooby. It is designed
+ * to be implemented by specific JSON library integrations (such as Jackson, Gson, Moshi, etc.) to
+ * adapt their internal parsing mechanics to Jooby's standard architecture.
+ *
+ *
Important Note: Jooby core itself does not implement these
+ * interfaces. These contracts act as a bridge and are designed to be implemented exclusively by
+ * dedicated JSON modules (such as {@code jooby-jackson}, {@code jooby-gson}, or {@code
+ * jooby-avaje-json}), etc.
+ *
+ * @since 4.5.0
+ * @author edgar
+ */
+@FunctionalInterface
+public interface JsonDecoder {
+
+ /**
+ * Decodes a JSON string into the specified Java {@link java.lang.reflect.Type}.
+ *
+ *
This is the primary decoding method that all underlying JSON libraries must implement. It
+ * accepts a raw reflection {@code Type}, making it capable of handling both simple classes and
+ * complex parameterized/generic types (e.g., {@code List}).
+ *
+ * @param json The JSON payload as a string.
+ * @param type The target Java reflection type to deserialize into.
+ * @return The deserialized Java object instance.
+ * @param The expected generic type of the returned object.
+ */
+ T decode(String json, Type type);
+
+ /**
+ * Decodes a JSON string into the specified Java {@link Class}.
+ *
+ * This is a convenience method for deserializing simple, non-generic types. It delegates
+ * directly to {@link #decode(String, Type)}.
+ *
+ * @param json The JSON payload as a string.
+ * @param type The target Java class to deserialize into.
+ * @return The deserialized Java object instance.
+ * @param The expected generic type of the returned object.
+ */
+ default T decode(String json, Class type) {
+ return decode(json, (java.lang.reflect.Type) type);
+ }
+
+ /**
+ * Decodes a JSON string into the specified Jooby {@link Reified} type.
+ *
+ * This is a convenience method for deserializing complex, generic types while avoiding type
+ * erasure. It extracts the underlying reflection type from the {@code Reified} token and
+ * delegates to {@link #decode(String, Type)}.
+ *
+ * @param json The JSON payload as a string.
+ * @param type The Reified type token capturing the target type.
+ * @return The deserialized Java object instance.
+ * @param The expected generic type of the returned object.
+ */
+ default T decode(String json, Reified type) {
+ return decode(json, type.getType());
+ }
+}
diff --git a/jooby/src/main/java/io/jooby/json/JsonEncoder.java b/jooby/src/main/java/io/jooby/json/JsonEncoder.java
new file mode 100644
index 0000000000..92e4a4a900
--- /dev/null
+++ b/jooby/src/main/java/io/jooby/json/JsonEncoder.java
@@ -0,0 +1,34 @@
+/*
+ * Jooby https://jooby.io
+ * Apache License Version 2.0 https://jooby.io/LICENSE.txt
+ * Copyright 2014 Edgar Espina
+ */
+package io.jooby.json;
+
+/**
+ * Contract for encoding (serializing) Java objects into JSON strings.
+ *
+ * Important Note: Jooby core itself does not implement these
+ * interfaces. These contracts act as a bridge and are designed to be implemented exclusively by
+ * dedicated JSON modules (such as {@code jooby-jackson}, {@code jooby-gson}, or {@code
+ * jooby-avaje-json}), etc.
+ *
+ * @since 4.5.0
+ * @author edgar
+ */
+@FunctionalInterface
+public interface JsonEncoder {
+
+ /**
+ * Encodes a Java object into its JSON string representation.
+ *
+ *
This method takes an arbitrary Java object and converts it into a valid JSON payload.
+ * Implementations are responsible for handling the specific serialization rules, configurations,
+ * and exception management of their underlying JSON library.
+ *
+ * @param value The Java object to serialize. This can be a simple data type, a collection, or a
+ * complex custom bean.
+ * @return The JSON string representation of the provided object.
+ */
+ String encode(Object value);
+}
diff --git a/jooby/src/main/java/io/jooby/json/package-info.java b/jooby/src/main/java/io/jooby/json/package-info.java
new file mode 100644
index 0000000000..170b354472
--- /dev/null
+++ b/jooby/src/main/java/io/jooby/json/package-info.java
@@ -0,0 +1,30 @@
+/**
+ * Provides the core JSON processing contracts and abstractions for the Jooby framework.
+ *
+ *
This package defines the foundational interfaces (such as {@link io.jooby.json.JsonEncoder},
+ * {@link io.jooby.json.JsonDecoder}, and {@link io.jooby.json.JsonCodec}) that allow Jooby to
+ * integrate seamlessly with various external JSON libraries (like Jackson, Gson, or Moshi). By
+ * implementing these contracts, those libraries can participate in Jooby's content negotiation,
+ * enabling automatic serialization of HTTP responses and deserialization of HTTP request bodies.
+ *
+ *
Null-Safety Guarantee
+ *
+ * This package is explicitly marked with {@link org.jspecify.annotations.NullMarked}. This
+ * establishes a strict nullability contract where all types (parameters, return types, and fields)
+ * within this package are considered non-null by default, unless explicitly
+ * annotated otherwise (e.g., using {@code @Nullable}).
+ *
+ *
Adopting JSpecify semantics ensures excellent interoperability with null-safe languages like
+ * Kotlin and provides robust guarantees for modern static code analysis tools.
+ *
+ *
Important Note: Jooby core itself does not implement these
+ * interfaces. These contracts act as a bridge and are designed to be implemented exclusively by
+ * dedicated JSON modules (such as {@code jooby-jackson}, {@code jooby-gson}, or {@code
+ * jooby-avaje-json}), etc.
+ *
+ * @see io.jooby.json.JsonCodec
+ * @author edgar
+ * @since 4.5.0
+ */
+@org.jspecify.annotations.NullMarked
+package io.jooby.json;
diff --git a/jooby/src/main/java/module-info.java b/jooby/src/main/java/module-info.java
index 077e1e792b..a1c1f12936 100644
--- a/jooby/src/main/java/module-info.java
+++ b/jooby/src/main/java/module-info.java
@@ -9,6 +9,7 @@
exports io.jooby;
exports io.jooby.annotation;
exports io.jooby.exception;
+ exports io.jooby.json;
exports io.jooby.handler;
exports io.jooby.validation;
exports io.jooby.problem;
diff --git a/modules/jooby-avaje-jsonb/src/main/java/io/jooby/avaje/jsonb/AvajeJsonCodec.java b/modules/jooby-avaje-jsonb/src/main/java/io/jooby/avaje/jsonb/AvajeJsonCodec.java
new file mode 100644
index 0000000000..56399e56f5
--- /dev/null
+++ b/modules/jooby-avaje-jsonb/src/main/java/io/jooby/avaje/jsonb/AvajeJsonCodec.java
@@ -0,0 +1,32 @@
+/*
+ * Jooby https://jooby.io
+ * Apache License Version 2.0 https://jooby.io/LICENSE.txt
+ * Copyright 2014 Edgar Espina
+ */
+package io.jooby.avaje.jsonb;
+
+import java.lang.reflect.Type;
+
+import io.avaje.jsonb.JsonType;
+import io.avaje.jsonb.Jsonb;
+import io.jooby.json.JsonCodec;
+
+class AvajeJsonCodec implements JsonCodec {
+ private final Jsonb jsonb;
+
+ public AvajeJsonCodec(Jsonb jsonb) {
+ this.jsonb = jsonb;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T decode(String json, Type type) {
+ var jsonType = (JsonType) jsonb.type(type);
+ return jsonType.fromJson(json);
+ }
+
+ @Override
+ public String encode(Object value) {
+ return jsonb.toJson(value);
+ }
+}
diff --git a/modules/jooby-avaje-jsonb/src/main/java/io/jooby/avaje/jsonb/AvajeJsonbModule.java b/modules/jooby-avaje-jsonb/src/main/java/io/jooby/avaje/jsonb/AvajeJsonbModule.java
index b28a2b1fbf..037ac75e11 100644
--- a/modules/jooby-avaje-jsonb/src/main/java/io/jooby/avaje/jsonb/AvajeJsonbModule.java
+++ b/modules/jooby-avaje-jsonb/src/main/java/io/jooby/avaje/jsonb/AvajeJsonbModule.java
@@ -14,6 +14,9 @@
import io.avaje.jsonb.Jsonb;
import io.jooby.*;
import io.jooby.internal.avaje.jsonb.*;
+import io.jooby.json.JsonCodec;
+import io.jooby.json.JsonDecoder;
+import io.jooby.json.JsonEncoder;
import io.jooby.output.Output;
/**
@@ -85,6 +88,11 @@ public void install(Jooby application) throws Exception {
var services = application.getServices();
services.put(Jsonb.class, jsonb);
+ // JsonCodec
+ var jsonCodec = new AvajeJsonCodec(jsonb);
+ services.putIfAbsent(JsonCodec.class, jsonCodec);
+ services.putIfAbsent(JsonEncoder.class, jsonCodec);
+ services.putIfAbsent(JsonDecoder.class, jsonCodec);
}
@Override
diff --git a/modules/jooby-avaje-jsonb/src/test/java/io/jooby/avaje/jsonb/AvajeJsonCodecTest.java b/modules/jooby-avaje-jsonb/src/test/java/io/jooby/avaje/jsonb/AvajeJsonCodecTest.java
new file mode 100644
index 0000000000..20320007b5
--- /dev/null
+++ b/modules/jooby-avaje-jsonb/src/test/java/io/jooby/avaje/jsonb/AvajeJsonCodecTest.java
@@ -0,0 +1,72 @@
+/*
+ * Jooby https://jooby.io
+ * Apache License Version 2.0 https://jooby.io/LICENSE.txt
+ * Copyright 2014 Edgar Espina
+ */
+package io.jooby.avaje.jsonb;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.avaje.jsonb.Jsonb;
+import io.jooby.Reified;
+
+class AvajeJsonCodecTest {
+
+ private AvajeJsonCodec codec;
+
+ @BeforeEach
+ void setUp() {
+ Jsonb jsonb = Jsonb.builder().build();
+ codec = new AvajeJsonCodec(jsonb);
+ }
+
+ @Test
+ void shouldEncodeMapToJson() {
+ // Using a LinkedHashMap guarantees the order of the keys in the output JSON
+ Map map = new java.util.LinkedHashMap<>();
+ map.put("Alice", 30);
+ map.put("Bob", 25);
+
+ String json = codec.encode(map);
+
+ assertEquals("{\"Alice\":30,\"Bob\":25}", json);
+ }
+
+ @Test
+ void shouldDecodeJsonToGenericMapUsingReified() {
+ String json = "{\"Alice\":30,\"Bob\":25}";
+
+ // Using the anonymous subclass trick to capture Map without type erasure
+ Map map = codec.decode(json, Reified.map(String.class, Integer.class));
+
+ assertNotNull(map);
+ assertEquals(2, map.size());
+
+ assertEquals(30, map.get("Alice"));
+ assertEquals(25, map.get("Bob"));
+ }
+
+ @Test
+ void shouldDecodeJsonToGenericListMapUsingReified() {
+ String json = "[{\"Alice\":30,\"Bob\":25}]";
+
+ // Using the anonymous subclass trick to capture Map without type erasure
+ List