You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
GH-3930: Add Jackson 3 support; deprecate Jackson 2
Fixes#3930
Issue link: #3930
This commit introduces comprehensive Jackson 3 support across the Spring for Apache Kafka framework
while maintaining backward compatibility by deprecating Jackson 2 components.
BREAKING CHANGES:
- All Jackson 2-based classes are now deprecated and will be removed in a future version
- Default Jackson version updated from 2.19.2 to 2.19.1 for Jackson 2
- Added Jackson 3 BOM dependency (3.0.0-rc5)
NEW FEATURES:
- Added complete Jackson 3 counterparts for all existing Jackson 2 classes:
* JsonKafkaHeaderMapper (replaces DefaultKafkaHeaderMapper)
* JacksonJsonSerializer/Deserializer (replaces JsonSerializer/Deserializer)
* JacksonJsonSerde (replaces JsonSerde)
* JacksonJsonMessageConverter and subclasses (replaces JsonMessageConverter family)
* JacksonProjectingMessageConverter (replaces ProjectingMessageConverter)
* DefaultJacksonJavaTypeMapper (replaces DefaultJackson2JavaTypeMapper)
* Jackson3Utils and Jackson3MimeTypeModule utilities
INFRASTRUCTURE:
- Enhanced JacksonPresent utility to detect Jackson 3 availability
- Updated MessagingMessageConverter and BatchMessagingMessageConverter to prefer Jackson 3
- Modified RecordMessagingMessageListenerAdapter to handle new converter types
- Updated all serialization/deserialization logic to use Jackson 3 APIs
DOCUMENTATION:
- Updated all documentation references from Jackson 2 to Jackson 3 classes
- Corrected sample code and configuration examples
- Updated method signatures and class references throughout documentation
TESTING:
- Migrated all test cases to use Jackson 3 equivalents
- Updated test configurations and mock setups
- Maintained test coverage for both Jackson 2 (deprecated) and Jackson 3 paths
The framework now automatically detects and prefers Jackson 3 when available, falling back to Jackson 2 for backward compatibility.
All existing applications will continue to work with Jackson 2, but new development should migrate to Jackson 3 classes.
Signed-off-by: Soby Chacko <[email protected]>
Copy file name to clipboardExpand all lines: samples/sample-01/README.adoc
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
This sample demonstrates a simple producer and consumer; the producer sends objects of type `Foo1` and the consumer receives objects of type `Foo2` (the objects have the same field, `foo`).
4
4
5
-
The producer uses a `JsonSerializer`; the consumer uses the `ByteArrayDeserializer`, together with a `JsonMessageConverter` which converts to the type of the listener method argument.
5
+
The producer uses a `JacksonJsonSerializer`; the consumer uses the `ByteArrayDeserializer`, together with a `JacksonJsonMessageConverter` which converts to the type of the listener method argument.
6
6
7
7
Run the application and use curl to send some data:
Copy file name to clipboardExpand all lines: samples/sample-02/README.adoc
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
This sample demonstrates a simple producer and a multi-method consumer; the producer sends objects of types `Foo1` and `Bar1` and the consumer receives objects of type `Foo2` and `Bar2` (the objects have the same field, `foo`).
4
4
5
-
The producer uses a `JsonSerializer`; the consumer uses a `ByteArrayDeserializer`, together with a `ByteArrayJsonMessageConverter` which converts to the required type of the listener method argument.
5
+
The producer uses a `JacksonJsonSerializer`; the consumer uses a `ByteArrayJacksonDeserializer`, together with a `ByteArrayJacksonJsonMessageConverter` which converts to the required type of the listener method argument.
6
6
We can't infer the type in this case (because the type is used to choose the method to call).
7
7
We therefore configure type mapping on the producer and consumer side.
8
8
See the `application.yml` for the producer side and the `converter` bean on the consumer side.
Copy file name to clipboardExpand all lines: spring-kafka-docs/src/main/antora/modules/ROOT/pages/appendix/change-history.adoc
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1083,7 +1083,7 @@ Previously, they were mapped as JSON and only `MimeType` was decoded.
1083
1083
`MediaType` could not be decoded.
1084
1084
They are now simple strings for interoperability.
1085
1085
1086
-
Also, the `DefaultKafkaHeaderMapper` has a new `addToStringClasses` method, allowing the specification of types that should be mapped by using `toString()` instead of JSON.
1086
+
Also, the `JsonKafkaHeaderMapper` has a new `addToStringClasses` method, allowing the specification of types that should be mapped by using `toString()` instead of JSON.
1087
1087
See xref:kafka/headers.adoc[Message Headers] for more information.
Copy file name to clipboardExpand all lines: spring-kafka-docs/src/main/antora/modules/ROOT/pages/kafka/annotation-error-handling.adoc
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -563,7 +563,7 @@ int delivery = ByteBuffer.wrap(record.headers()
563
563
.getInt();
564
564
----
565
565
566
-
When using `@KafkaListener` with the `DefaultKafkaHeaderMapper` or `SimpleKafkaHeaderMapper`, it can be obtained by adding `@Header(KafkaHeaders.DELIVERY_ATTEMPT) int delivery` as a parameter to the listener method.
566
+
When using `@KafkaListener` with the `JsonKafkaHeaderMapper` or `SimpleKafkaHeaderMapper`, it can be obtained by adding `@Header(KafkaHeaders.DELIVERY_ATTEMPT) int delivery` as a parameter to the listener method.
567
567
568
568
To enable population of this header, set the container property `deliveryAttemptHeader` to `true`.
569
569
It is disabled by default to avoid the (small) overhead of looking up the state for each record and adding the header.
Copy file name to clipboardExpand all lines: spring-kafka-docs/src/main/antora/modules/ROOT/pages/kafka/headers.adoc
+16-16Lines changed: 16 additions & 16 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -36,7 +36,7 @@ public interface KafkaHeaderMapper {
36
36
37
37
The `SimpleKafkaHeaderMapper` maps raw headers as `byte[]`, with configuration options for conversion to `String` values.
38
38
39
-
The `DefaultKafkaHeaderMapper` maps the key to the `MessageHeaders` header name and, in order to support rich header types for outbound messages, JSON conversion is performed.
39
+
The `JsonKafkaHeaderMapper` maps the key to the `MessageHeaders` header name and, in order to support rich header types for outbound messages, JSON conversion is performed.
40
40
A +++"+++`special`+++"+++ header (with a key of `spring_json_header_types`) contains a JSON map of `<key>:<type>`.
41
41
This header is used on the inbound side to provide appropriate conversion of each header value to the original type.
42
42
@@ -48,19 +48,19 @@ The following listing shows a number of example mappings:
48
48
49
49
[source, java]
50
50
----
51
-
public DefaultKafkaHeaderMapper() { <1>
51
+
public JsonKafkaHeaderMapper() { <1>
52
52
...
53
53
}
54
54
55
-
public DefaultKafkaHeaderMapper(ObjectMapper objectMapper) { <2>
55
+
public JsonKafkaHeaderMapper(ObjectMapper objectMapper) { <2>
56
56
...
57
57
}
58
58
59
-
public DefaultKafkaHeaderMapper(String... patterns) { <3>
59
+
public JsonKafkaHeaderMapper(String... patterns) { <3>
60
60
...
61
61
}
62
62
63
-
public DefaultKafkaHeaderMapper(ObjectMapper objectMapper, String... patterns) { <4>
63
+
public JsonKafkaHeaderMapper(ObjectMapper objectMapper, String... patterns) { <4>
64
64
...
65
65
}
66
66
----
@@ -95,7 +95,7 @@ The following test case illustrates this mechanism.
95
95
----
96
96
@Test
97
97
public void testSpecificStringConvert() {
98
-
DefaultKafkaHeaderMapper mapper = new DefaultKafkaHeaderMapper();
98
+
JsonKafkaHeaderMapper mapper = new JsonKafkaHeaderMapper();
99
99
Map<String, Boolean> rawMappedHeaders = new HashMap<>();
100
100
rawMappedHeaders.put("thisOnesAString", true);
101
101
rawMappedHeaders.put("thisOnesBytes", false);
@@ -126,10 +126,10 @@ To create a mapper for inbound mapping, use one of the static methods on the res
126
126
127
127
[source, java]
128
128
----
129
-
public static DefaultKafkaHeaderMapper forInboundOnlyWithMatchers(String... patterns) {
129
+
public static JsonKafkaHeaderMapper forInboundOnlyWithMatchers(String... patterns) {
130
130
}
131
131
132
-
public static DefaultKafkaHeaderMapper forInboundOnlyWithMatchers(ObjectMapper objectMapper, String... patterns) {
132
+
public static JsonKafkaHeaderMapper forInboundOnlyWithMatchers(ObjectMapper objectMapper, String... patterns) {
133
133
}
134
134
135
135
public static SimpleKafkaHeaderMapper forInboundOnlyWithMatchers(String... patterns) {
This will exclude all headers beginning with `abc` and include all others.
147
147
148
-
By default, the `DefaultKafkaHeaderMapper` is used in the `MessagingMessageConverter` and `BatchMessagingMessageConverter`, as long as Jackson is on the classpath.
148
+
By default, the `JsonKafkaHeaderMapper` is used in the `MessagingMessageConverter` and `BatchMessagingMessageConverter`, as long as Jackson is on the classpath.
149
149
150
150
With the batch converter, the converted headers are available in the `KafkaHeaders.BATCH_CONVERTED_HEADERS` as a `List<Map<String, Object>>` where the map in a position of the list corresponds to the data position in the payload.
151
151
152
152
If there is no converter (either because Jackson is not present or it is explicitly set to `null`), the headers from the consumer record are provided unconverted in the `KafkaHeaders.NATIVE_HEADERS` header.
153
153
This header is a `Headers` object (or a `List<Headers>` in the case of the batch converter), where the position in the list corresponds to the data position in the payload.
154
154
155
155
IMPORTANT: Certain types are not suitable for JSON serialization, and a simple `toString()` serialization might be preferred for these types.
156
-
The `DefaultKafkaHeaderMapper` has a method called `addToStringClasses()` that lets you supply the names of classes that should be treated this way for outbound mapping.
156
+
The `JsonKafkaHeaderMapper` has a method called `addToStringClasses()` that lets you supply the names of classes that should be treated this way for outbound mapping.
157
157
During inbound mapping, they are mapped as `String`.
158
158
By default, only `org.springframework.util.MimeType` and `org.springframework.http.MediaType` are mapped this way.
159
159
@@ -170,7 +170,7 @@ When all applications are using 2.3 or higher, you can leave the property at its
170
170
@Bean
171
171
MessagingMessageConverter converter() {
172
172
MessagingMessageConverter converter = new MessagingMessageConverter();
173
-
DefaultKafkaHeaderMapper mapper = new DefaultKafkaHeaderMapper();
173
+
JsonKafkaHeaderMapper mapper = new JsonKafkaHeaderMapper();
174
174
mapper.setEncodeStrings(true);
175
175
converter.setHeaderMapper(mapper);
176
176
return converter;
@@ -187,16 +187,16 @@ Starting with 4.0, multi-value header mapping is supported, where the same logic
187
187
By default, the `HeaderMapper` does **not** create multiple Kafka headers with the same name.
188
188
Instead, when it encounters a collection value (e.g., a `List<byte[]>`), it serializes the entire collection into **one** Kafka header whose value is a JSON array.
189
189
190
-
* **Producer side:** `DefaultKafkaHeaderMapper` writes the JSON bytes, while `SimpleKafkaHeaderMapper` ignore it.
190
+
* **Producer side:** `JsonKafkaHeaderMapper` writes the JSON bytes, while `SimpleKafkaHeaderMapper` ignore it.
191
191
* **Consumer side:** the mapper exposes the header as a single value—the **last occurrence wins**; earlier duplicates are silently discarded.
192
192
193
193
Preserving each individual header requires explicit registration of patterns that designate the header as multi‑valued.
194
194
195
-
`DefaultKafkaHeaderMapper#setMultiValueHeaderPatterns(String... patterns)` accepts a list of patterns, which can be either wildcard expressions or exact header names.
195
+
`JsonKafkaHeaderMapper#setMultiValueHeaderPatterns(String... patterns)` accepts a list of patterns, which can be either wildcard expressions or exact header names.
196
196
197
197
[source, java]
198
198
----
199
-
DefaultKafkaHeaderMapper mapper = new DefaultKafkaHeaderMapper();
199
+
JsonKafkaHeaderMapper mapper = new JsonKafkaHeaderMapper();
@@ -214,5 +214,5 @@ NOTE: Regular expressions are *not* supported; only the +*+ wildcard is allowed
214
214
215
215
[IMPORTANT]
216
216
====
217
-
On the *Producer Side*, When `DefaultKafkaHeaderMapper` serializes a multi-value header, every element in that collection must be of a single Java type—mixing, for example, `String` and `byte[]` values under a single header key will lead to a conversion error.
217
+
On the *Producer Side*, When `JsonKafkaHeaderMapper` serializes a multi-value header, every element in that collection must be of a single Java type—mixing, for example, `String` and `byte[]` values under a single header key will lead to a conversion error.
Copy file name to clipboardExpand all lines: spring-kafka-docs/src/main/antora/modules/ROOT/pages/kafka/receiving-messages/class-level-kafkalistener.adoc
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -35,7 +35,7 @@ static class MultiListenerBean {
35
35
Starting with version 2.1.3, you can designate a `@KafkaHandler` method as the default method that is invoked if there is no match on other methods.
36
36
At most, one method can be so designated.
37
37
When using `@KafkaHandler` methods, the payload must have already been converted to the domain object (so the match can be performed).
38
-
Use a custom deserializer, the `JsonDeserializer`, or the `JsonMessageConverter` with its `TypePrecedence` set to `TYPE_ID`.
38
+
Use a custom deserializer, the `JacksonJsonDeserializer`, or the `JacksonJsonMessageConverter` with its `TypePrecedence` set to `TYPE_ID`.
39
39
See xref:kafka/serdes.adoc[Serialization, Deserialization, and Message Conversion] for more information.
40
40
41
41
IMPORTANT: Due to some limitations in the way Spring resolves method arguments, a default `@KafkaHandler` cannot receive discrete headers; it must use the `ConsumerRecordMetadata` as discussed in xref:kafka/receiving-messages/listener-annotation.adoc#consumer-record-metadata[Consumer Record Metadata].
Copy file name to clipboardExpand all lines: spring-kafka-docs/src/main/antora/modules/ROOT/pages/kafka/sending-messages.adoc
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -538,7 +538,7 @@ The `Header` contains a four-byte int (big-endian).
538
538
The server must use this header to route the reply to the correct partition (`@KafkaListener` does this).
539
539
In this case, though, the reply container must not use Kafka's group management feature and must be configured to listen on a fixed partition (by using a `TopicPartitionOffset` in its `ContainerProperties` constructor).
540
540
541
-
NOTE: The `DefaultKafkaHeaderMapper` requires Jackson to be on the classpath (for the `@KafkaListener`).
541
+
NOTE: The `JsonKafkaHeaderMapper` requires Jackson to be on the classpath (for the `@KafkaListener`).
542
542
If it is not available, the message converter has no header mapper, so you must configure a `MessagingMessageConverter` with a `SimpleKafkaHeaderMapper`, as shown earlier.
0 commit comments