From f35245c752a80cef01166473d2eff3997efff802 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Fri, 14 Nov 2025 20:11:17 +0100 Subject: [PATCH] Migrate to Jackson3 Jackson2 ist still needed as runtime optional dependency for users that still use the old RestClient. Signed-off-by: Peter-Josef Meisch --- pom.xml | 17 +++++++++++- .../elc/ElasticsearchConfiguration.java | 17 ++++++------ ...icsearchLegacyRestClientConfiguration.java | 2 +- .../ReactiveElasticsearchConfiguration.java | 17 ++++++------ .../elasticsearch/core/document/Document.java | 5 ++-- .../core/document/MapDocument.java | 8 +++--- .../core/geo/CustomGeoModule.java | 27 +++++++++---------- .../core/index/GeoShapeMappingParameters.java | 4 +-- .../core/index/MappingBuilder.java | 20 +++++++------- .../core/index/MappingParameters.java | 8 +++--- .../support/DefaultStringObjectMap.java | 11 ++++---- 11 files changed, 74 insertions(+), 62 deletions(-) diff --git a/pom.xml b/pom.xml index 30c9984cbe..cc7d103fb7 100644 --- a/pom.xml +++ b/pom.xml @@ -158,17 +158,32 @@ + + tools.jackson.core + jackson-core + 3.0.2 + + + tools.jackson.core + jackson-databind + 3.0.2 + + + com.fasterxml.jackson.core jackson-core + provided + true com.fasterxml.jackson.core jackson-databind + provided + true - javax.interceptor javax.interceptor-api diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchConfiguration.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchConfiguration.java index f4a90fff52..05df5632d3 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchConfiguration.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchConfiguration.java @@ -17,12 +17,14 @@ import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.json.JsonpMapper; -import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.json.jackson.Jackson3JsonpMapper; import co.elastic.clients.transport.ElasticsearchTransport; import co.elastic.clients.transport.TransportOptions; import co.elastic.clients.transport.rest5_client.Rest5ClientOptions; import co.elastic.clients.transport.rest5_client.low_level.RequestOptions; import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; +import tools.jackson.databind.cfg.JsonNodeFeature; +import tools.jackson.databind.json.JsonMapper; import org.springframework.context.annotation.Bean; import org.springframework.data.elasticsearch.client.ClientConfiguration; @@ -32,10 +34,6 @@ import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.util.Assert; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; - /** * Base class for a @{@link org.springframework.context.annotation.Configuration} class to set up the Elasticsearch * connection using the Elasticsearch Client. This class exposes different parts of the setup as Spring beans. Deriving @@ -128,10 +126,11 @@ public JsonpMapper jsonpMapper() { // we need to create our own objectMapper that keeps null values in order to provide the storeNullValue // functionality. The one Elasticsearch would provide removes the nulls. We remove unwanted nulls before they get // into this mapper, so we can safely keep them here. - var objectMapper = (new ObjectMapper()) - .configure(SerializationFeature.INDENT_OUTPUT, false) - .setSerializationInclusion(JsonInclude.Include.ALWAYS); - return new JacksonJsonpMapper(objectMapper); + JsonMapper jsonMapper = JsonMapper.builder() + .enable(JsonNodeFeature.WRITE_NULL_PROPERTIES) + .enable(JsonNodeFeature.READ_NULL_PROPERTIES) + .build(); + return new Jackson3JsonpMapper(jsonMapper); } /** diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchLegacyRestClientConfiguration.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchLegacyRestClientConfiguration.java index 2005faf5b9..1bdaba524a 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchLegacyRestClientConfiguration.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchLegacyRestClientConfiguration.java @@ -47,7 +47,7 @@ * @since 4.4 * @deprecated since 6.0, use {@link ElasticsearchConfiguration} */ -@Deprecated(since = "6.0", forRemoval=true) +@Deprecated(since = "6.0", forRemoval = true) public abstract class ElasticsearchLegacyRestClientConfiguration extends ElasticsearchConfigurationSupport { /** diff --git a/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchConfiguration.java b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchConfiguration.java index d0198ca9f8..96e39f244b 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchConfiguration.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchConfiguration.java @@ -16,12 +16,14 @@ package org.springframework.data.elasticsearch.client.elc; import co.elastic.clients.json.JsonpMapper; -import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.json.jackson.Jackson3JsonpMapper; import co.elastic.clients.transport.ElasticsearchTransport; import co.elastic.clients.transport.TransportOptions; import co.elastic.clients.transport.rest5_client.Rest5ClientOptions; import co.elastic.clients.transport.rest5_client.low_level.RequestOptions; import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; +import tools.jackson.databind.cfg.JsonNodeFeature; +import tools.jackson.databind.json.JsonMapper; import org.elasticsearch.client.RestClient; import org.springframework.context.annotation.Bean; @@ -32,10 +34,6 @@ import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.util.Assert; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; - /** * Base class for a @{@link org.springframework.context.annotation.Configuration} class to set up the Elasticsearch * connection using the {@link ReactiveElasticsearchClient}. This class exposes different parts of the setup as Spring @@ -127,10 +125,11 @@ public JsonpMapper jsonpMapper() { // we need to create our own objectMapper that keeps null values in order to provide the storeNullValue // functionality. The one Elasticsearch would provide removes the nulls. We remove unwanted nulls before they get // into this mapper, so we can safely keep them here. - var objectMapper = (new ObjectMapper()) - .configure(SerializationFeature.INDENT_OUTPUT, false) - .setSerializationInclusion(JsonInclude.Include.ALWAYS); - return new JacksonJsonpMapper(objectMapper); + JsonMapper jsonMapper = JsonMapper.builder() + .enable(JsonNodeFeature.WRITE_NULL_PROPERTIES) + .enable(JsonNodeFeature.READ_NULL_PROPERTIES) + .build(); + return new Jackson3JsonpMapper(jsonMapper); } /** diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/Document.java b/src/main/java/org/springframework/data/elasticsearch/core/document/Document.java index db17704d07..d124acf76b 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/document/Document.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/Document.java @@ -15,7 +15,8 @@ */ package org.springframework.data.elasticsearch.core.document; -import java.io.IOException; +import tools.jackson.core.JacksonException; + import java.util.LinkedHashMap; import java.util.Map; import java.util.function.Function; @@ -87,7 +88,7 @@ default Document fromJson(String json) { clear(); try { putAll(MapDocument.OBJECT_MAPPER.readerFor(Map.class).readValue(json)); - } catch (IOException e) { + } catch (JacksonException e) { throw new ConversionException("Cannot parse JSON", e); } return this; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/MapDocument.java b/src/main/java/org/springframework/data/elasticsearch/core/document/MapDocument.java index 3b435e39b2..dc703ac1c9 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/document/MapDocument.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/MapDocument.java @@ -15,6 +15,9 @@ */ package org.springframework.data.elasticsearch.core.document; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; + import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; @@ -25,9 +28,6 @@ import org.springframework.data.elasticsearch.support.DefaultStringObjectMap; import org.springframework.data.mapping.MappingException; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - /** * {@link Document} implementation backed by a {@link LinkedHashMap}. * @@ -344,7 +344,7 @@ public void forEach(BiConsumer action) { public String toJson() { try { return OBJECT_MAPPER.writeValueAsString(this); - } catch (JsonProcessingException e) { + } catch (JacksonException e) { throw new MappingException("Cannot render document to JSON", e); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/geo/CustomGeoModule.java b/src/main/java/org/springframework/data/elasticsearch/core/geo/CustomGeoModule.java index d5560640e7..842e3d9c34 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/geo/CustomGeoModule.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/geo/CustomGeoModule.java @@ -1,26 +1,25 @@ package org.springframework.data.elasticsearch.core.geo; -import java.io.IOException; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.Version; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.module.SimpleModule; +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonGenerator; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueDeserializer; +import tools.jackson.databind.ValueSerializer; + import org.springframework.data.geo.Point; -class PointSerializer extends JsonSerializer { +class PointSerializer extends ValueSerializer { @Override - public void serialize(Point value, JsonGenerator gen, SerializerProvider serializers) throws IOException { - gen.writeObject(GeoPoint.fromPoint(value)); + public void serialize(Point value, JsonGenerator gen, SerializationContext serializers) throws JacksonException { + gen.writePOJO(GeoPoint.fromPoint(value)); } } -class PointDeserializer extends JsonDeserializer { +class PointDeserializer extends ValueDeserializer { @Override - public Point deserialize(JsonParser p, DeserializationContext context) throws IOException { + public Point deserialize(JsonParser p, DeserializationContext context) throws JacksonException { return GeoPoint.toPoint(p.readValueAs(GeoPoint.class)); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/GeoShapeMappingParameters.java b/src/main/java/org/springframework/data/elasticsearch/core/index/GeoShapeMappingParameters.java index 0abcebc834..eb1488a072 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/GeoShapeMappingParameters.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/GeoShapeMappingParameters.java @@ -15,14 +15,14 @@ */ package org.springframework.data.elasticsearch.core.index; +import tools.jackson.databind.node.ObjectNode; + import java.io.IOException; import org.jspecify.annotations.Nullable; import org.springframework.data.elasticsearch.annotations.GeoShapeField; import org.springframework.util.Assert; -import com.fasterxml.jackson.databind.node.ObjectNode; - /** * @author Peter-Josef Meisch */ diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java index 4dffe3909e..d7fe59b213 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java @@ -18,6 +18,13 @@ import static org.springframework.data.elasticsearch.core.index.MappingParameters.*; import static org.springframework.util.StringUtils.*; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.ObjectNode; +import tools.jackson.databind.node.StringNode; +import tools.jackson.databind.util.RawValue; + import java.io.IOException; import java.lang.annotation.Annotation; import java.nio.charset.Charset; @@ -48,13 +55,6 @@ import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.TextNode; -import com.fasterxml.jackson.databind.util.RawValue; - /** * @author Rizwan Idrees * @author Mohsin Husen @@ -184,7 +184,7 @@ protected String buildPropertyMapping(ElasticsearchPersistentEntity entity, if (!excludeFromSource.isEmpty()) { ObjectNode sourceNode = objectNode.putObject(SOURCE); ArrayNode excludes = sourceNode.putArray(SOURCE_EXCLUDES); - excludeFromSource.stream().map(TextNode::new).forEach(excludes::add); + excludeFromSource.stream().map(StringNode::new).forEach(excludes::add); } return objectMapper.writer().writeValueAsString(objectNode); @@ -238,7 +238,7 @@ private void mapEntity(ObjectNode objectNode, @Nullable ElasticsearchPersistentE if (mappingAnnotation.dynamicDateFormats().length > 0) { objectNode.putArray(DYNAMIC_DATE_FORMATS).addAll(Arrays.stream(mappingAnnotation.dynamicDateFormats()) - .map(TextNode::valueOf).collect(Collectors.toList())); + .map(StringNode::valueOf).collect(Collectors.toList())); } if (runtimeFields != null) { @@ -546,7 +546,7 @@ private void addJoinFieldMapping(ObjectNode propertiesNode, ElasticsearchPersist if (children.length > 1) { relationsNode.putArray(parent) - .addAll(Arrays.stream(children).map(TextNode::valueOf).collect(Collectors.toList())); + .addAll(Arrays.stream(children).map(StringNode::valueOf).collect(Collectors.toList())); } else if (children.length == 1) { relationsNode.put(parent, children[0]); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java index b4eab12aff..01a23d558e 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java @@ -15,6 +15,9 @@ */ package org.springframework.data.elasticsearch.core.index; +import tools.jackson.databind.node.ObjectNode; +import tools.jackson.databind.node.StringNode; + import java.io.IOException; import java.lang.annotation.Annotation; import java.util.ArrayList; @@ -28,9 +31,6 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.TextNode; - /** * A class to hold the mapping parameters that might be set on * {@link org.springframework.data.elasticsearch.annotations.Field } or @@ -285,7 +285,7 @@ public void writeTypeAndParametersTo(ObjectNode objectNode) throws IOException { if (copyTo != null && copyTo.length > 0) { objectNode.putArray(FIELD_PARAM_COPY_TO) - .addAll(Arrays.stream(copyTo).map(TextNode::valueOf).collect(Collectors.toList())); + .addAll(Arrays.stream(copyTo).map(StringNode::valueOf).collect(Collectors.toList())); } if (ignoreAbove != null) { diff --git a/src/main/java/org/springframework/data/elasticsearch/support/DefaultStringObjectMap.java b/src/main/java/org/springframework/data/elasticsearch/support/DefaultStringObjectMap.java index f171d86048..aadfbef50f 100644 --- a/src/main/java/org/springframework/data/elasticsearch/support/DefaultStringObjectMap.java +++ b/src/main/java/org/springframework/data/elasticsearch/support/DefaultStringObjectMap.java @@ -15,7 +15,9 @@ */ package org.springframework.data.elasticsearch.support; -import java.io.IOException; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.ObjectMapper; + import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; @@ -25,9 +27,6 @@ import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - /** * @author Peter-Josef Meisch */ @@ -48,7 +47,7 @@ public DefaultStringObjectMap(Map map) { public String toJson() { try { return OBJECT_MAPPER.writeValueAsString(this); - } catch (JsonProcessingException e) { + } catch (JacksonException e) { throw new IllegalArgumentException("Cannot render document to JSON", e); } } @@ -61,7 +60,7 @@ public T fromJson(String json) { delegate.clear(); try { delegate.putAll(OBJECT_MAPPER.readerFor(Map.class).readValue(json)); - } catch (IOException e) { + } catch (JacksonException e) { throw new IllegalArgumentException("Cannot parse JSON", e); } return (T) this;