From 9ca91426fbd039b515411643b729200d5232ebc6 Mon Sep 17 00:00:00 2001 From: Ralph Ursprung Date: Tue, 10 Jun 2025 11:05:45 +0200 Subject: [PATCH 1/2] jackson `ObjectMapper`: auto-detect modules jackson 2.x still supports java 7 and thus does not automatically support java 8 classes. this will change with jackson 3.x (see e.g. this [PR adding JSR310 date support] to 3.x). PR #251 added documentation on how a custom `ObjectMapper` can be registered - but there's no reason why we can't just enable auto-detection centrally. the docs mention that performance should be considered, but this is IMHO not relevant here since we only construct it once and then keep the same `ObjectMapper` (which in turn the docs mention as best practices). Signed-off-by: Ralph Ursprung [PR adding JSR310 date support]: https://github.com/FasterXML/jackson-databind/pull/5032 --- CHANGELOG.md | 1 + USER_GUIDE.md | 8 +------- .../client/json/jackson/JacksonJsonpMapper.java | 4 +++- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index baa6473cf0..ac8435d0bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Added - Added support for Index Management plugin APIs ([#1604](https://github.com/opensearch-project/opensearch-java/pull/1604)) - Added support for the security plugin APIs ([#1601](https://github.com/opensearch-project/opensearch-java/pull/1601)) +- Jackson `ObjectMapper` modules are now being auto-detected ([#1614](https://github.com/opensearch-project/opensearch-java/pull/1614)) ### Dependencies - Bump `org.owasp.dependencycheck` from 12.1.1 to 12.1.3 ([#1608](https://github.com/opensearch-project/opensearch-java/pull/1608), [#1607](https://github.com/opensearch-project/opensearch-java/pull/1607), [#1623](https://github.com/opensearch-project/opensearch-java/pull/1623)) diff --git a/USER_GUIDE.md b/USER_GUIDE.md index dfe1399bfd..fd1a1bfec4 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -99,13 +99,7 @@ OpenSearchTransport transport = new RestClientTransport(restClient, new JacksonJ OpenSearchClient client = new OpenSearchClient(transport); ``` -The `JacksonJsonpMapper` class (2.x versions) only supports Java 7 objects by default. [Java 8 modules](https://github.com/FasterXML/jackson-modules-java8) to support JDK8 classes such as the Date and Time API (JSR-310), `Optional`, and more can be used by including [the additional datatype dependency](https://github.com/FasterXML/jackson-modules-java8#usage) and adding the module. For example, to include JSR-310 classes: - -```java -OpenSearchTransport transport = new RestClientTransport(restClient, - new JacksonJsonpMapper(new ObjectMapper().registerModule(new JavaTimeModule()))); -OpenSearchClient client = new OpenSearchClient(transport); -``` +The `JacksonJsonpMapper` class (2.x versions) only supports Java 7 objects by default. [Java 8 modules](https://github.com/FasterXML/jackson-modules-java8) to support JDK8 classes such as the Date and Time API (JSR-310), `Optional`, and more can be used by including [the additional datatype dependency](https://github.com/FasterXML/jackson-modules-java8#usage) and adding the module. Auto-detection for these modules is enabled, adding them to the classpath is enough. Upcoming OpenSearch `3.0.0` release brings HTTP/2 support and as such, the `RestClientTransport` would switch to HTTP/2 if available (for both HTTPS and/or HTTP protocols). The desired protocol could be forced using `RestClientBuilder.HttpClientConfigCallback`. diff --git a/java-client/src/main/java/org/opensearch/client/json/jackson/JacksonJsonpMapper.java b/java-client/src/main/java/org/opensearch/client/json/jackson/JacksonJsonpMapper.java index c10379abc2..c27c499a28 100644 --- a/java-client/src/main/java/org/opensearch/client/json/jackson/JacksonJsonpMapper.java +++ b/java-client/src/main/java/org/opensearch/client/json/jackson/JacksonJsonpMapper.java @@ -63,7 +63,9 @@ public JacksonJsonpMapper(ObjectMapper objectMapper, JsonFactory jsonFactory) { public JacksonJsonpMapper() { this( - new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, false).setSerializationInclusion(JsonInclude.Include.NON_NULL) + new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, false) + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .findAndRegisterModules() ); } From c834c145a6bd0fb80a7dec1dc45c461ebe99e2a8 Mon Sep 17 00:00:00 2001 From: Ralph Ursprung Date: Tue, 10 Jun 2025 11:14:03 +0200 Subject: [PATCH 2/2] jackson `ObjectMapper`: add JDK8 & JSR310 modules without these various core java types introduced with JDK8 (more than a decade ago!) are not usable. we should enable them centrally so that not everyone has to do this manually. note that with jackson 3.x this will no longer be needed as it includes it by default: https://github.com/FasterXML/jackson-databind/issues/982 Signed-off-by: Ralph Ursprung --- CHANGELOG.md | 1 + USER_GUIDE.md | 2 +- java-client/build.gradle.kts | 2 ++ .../opensearch/integTest/AbstractCrudIT.java | 35 +++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac8435d0bf..db5d730b38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Added support for Index Management plugin APIs ([#1604](https://github.com/opensearch-project/opensearch-java/pull/1604)) - Added support for the security plugin APIs ([#1601](https://github.com/opensearch-project/opensearch-java/pull/1601)) - Jackson `ObjectMapper` modules are now being auto-detected ([#1614](https://github.com/opensearch-project/opensearch-java/pull/1614)) +- Jackson `ObjectMapper` JDK8 & JSR310 modules are now always present ([#1614](https://github.com/opensearch-project/opensearch-java/pull/1614)) ### Dependencies - Bump `org.owasp.dependencycheck` from 12.1.1 to 12.1.3 ([#1608](https://github.com/opensearch-project/opensearch-java/pull/1608), [#1607](https://github.com/opensearch-project/opensearch-java/pull/1607), [#1623](https://github.com/opensearch-project/opensearch-java/pull/1623)) diff --git a/USER_GUIDE.md b/USER_GUIDE.md index fd1a1bfec4..c621088565 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -99,7 +99,7 @@ OpenSearchTransport transport = new RestClientTransport(restClient, new JacksonJ OpenSearchClient client = new OpenSearchClient(transport); ``` -The `JacksonJsonpMapper` class (2.x versions) only supports Java 7 objects by default. [Java 8 modules](https://github.com/FasterXML/jackson-modules-java8) to support JDK8 classes such as the Date and Time API (JSR-310), `Optional`, and more can be used by including [the additional datatype dependency](https://github.com/FasterXML/jackson-modules-java8#usage) and adding the module. Auto-detection for these modules is enabled, adding them to the classpath is enough. +Auto-detection for `JsonMapper` modules is enabled, adding them to the classpath is enough. JSR310 & JDK8 support are enabled by default. Upcoming OpenSearch `3.0.0` release brings HTTP/2 support and as such, the `RestClientTransport` would switch to HTTP/2 if available (for both HTTPS and/or HTTP protocols). The desired protocol could be forced using `RestClientBuilder.HttpClientConfigCallback`. diff --git a/java-client/build.gradle.kts b/java-client/build.gradle.kts index 3a8c7ef001..50f59ff826 100644 --- a/java-client/build.gradle.kts +++ b/java-client/build.gradle.kts @@ -218,6 +218,8 @@ dependencies { // Apache 2.0 implementation("com.fasterxml.jackson.core", "jackson-core", jacksonVersion) implementation("com.fasterxml.jackson.core", "jackson-databind", jacksonDatabindVersion) + implementation("com.fasterxml.jackson.datatype", "jackson-datatype-jsr310", jacksonDatabindVersion) + implementation("com.fasterxml.jackson.datatype", "jackson-datatype-jdk8", jacksonDatabindVersion) testImplementation("com.fasterxml.jackson.datatype", "jackson-datatype-jakarta-jsonp", jacksonVersion) // For AwsSdk2Transport diff --git a/java-client/src/test/java11/org/opensearch/client/opensearch/integTest/AbstractCrudIT.java b/java-client/src/test/java11/org/opensearch/client/opensearch/integTest/AbstractCrudIT.java index 0020a118eb..50a6abaf63 100644 --- a/java-client/src/test/java11/org/opensearch/client/opensearch/integTest/AbstractCrudIT.java +++ b/java-client/src/test/java11/org/opensearch/client/opensearch/integTest/AbstractCrudIT.java @@ -9,9 +9,11 @@ package org.opensearch.client.opensearch.integTest; import java.io.IOException; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormat; @@ -604,9 +606,34 @@ public void testGetIdWithPlusSign() throws Exception { } } + public void testJacksonJava8TypesSerDe() throws Exception { + final String id = "java8"; + final AppData appData = new AppData(); + // use a date where month & day could be swapped depending on the format to ensure that this can't happen. + // i.e. month != day and day <= 12. + final LocalDateTime testDate = LocalDateTime.of(2000, 12, 1, 0, 0); + appData.setDate(Optional.of(testDate)); + + { + final IndexResponse indexResponse = javaClient().index(b -> b.index("index").id(id).document(appData)); + assertEquals("index", indexResponse.index()); + assertEquals(id, indexResponse.id()); + } + { + final GetResponse getResponse = javaClient().get(b -> b.index("index").id(id), AppData.class); + assertTrue(getResponse.found()); + assertEquals("index", getResponse.index()); + assertEquals(id, getResponse.id()); + assertNotNull(getResponse.source()); + assertTrue(getResponse.source().getDate().isPresent()); + assertEquals(testDate, getResponse.source().getDate().get()); + } + } + public static class AppData { private int intValue; private String msg; + private Optional date = Optional.empty(); public int getIntValue() { return intValue; @@ -623,5 +650,13 @@ public String getMsg() { public void setMsg(String msg) { this.msg = msg; } + + public Optional getDate() { + return date; + } + + public void setDate(Optional date) { + this.date = date; + } } }