From b0787956c2d39de4fa39bbd20d68ab5209632c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20BEAUDOIN?= Date: Thu, 20 Aug 2026 12:10:22 +0200 Subject: [PATCH] fix(java): stop hardcoding failOnUnknownProperties, and turn it off MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `templates/Java/libraries/jersey3/JSON.mustache` replaced the generator's `{{failOnUnknownProperties}}` placeholder with a literal `true`, so the built client rejects any response property it does not know: UnrecognizedPropertyException: Unrecognized field "some_field_from_a_newer_server" (class …model.SearchResponse), not marked as ignorable That is not a degraded read — the whole response is lost over one extra field. Three things in this repository already say it should be otherwise: * the generator's own default for this option is false, and hardcoding the value means `--additional-properties failOnUnknownProperties=…` cannot reach it at all; * every other Java library template here leaves unknown properties alone — apache-httpclient, native, restclient, vertx, feign, resteasy, rest-assured, webclient, google-api-client, and the shared ApiClient.mustache; * the schema declares `additionalProperties: true` on `searchResponse`, `aggBucketsResult` and `aggBucket`. The generated Python client honours it (`additional_properties`); the Java client contradicts it. And the README's compatibility table promises that a client stays PARTIALLY compatible with a newer Manticore Search. With strict deserialization that state cannot exist: the first response field a newer server adds turns every search into an exception. Restore the placeholder on the jersey3 template (the library `build.sh` builds with) and pass the option explicitly, so the value is chosen in one visible place rather than frozen in a template. jersey2 and retrofit2 carry the same hardcoded literal; they are left alone here since nothing builds with them. This makes an unknown property harmless, not readable — a field absent from the schema is dropped. Fields that callers need still have to be modelled; this only stops one of them from costing the response. Verified by regenerating with OpenAPI Generator 7.17.0 (the version in ./generator-versions), confirming the rendered `JSON.java` now configures the feature to false, and running the new test against the regenerated output. Both cases in it throw on the published 10.2.0 client. --- build.sh | 1 + .../Java/libraries/jersey3/JSON.mustache | 2 +- .../java/api/UnknownResponsePropertyTest.java | 47 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 test/java/api/UnknownResponsePropertyTest.java diff --git a/build.sh b/build.sh index 0a5df0a8..b0ef7365 100755 --- a/build.sh +++ b/build.sh @@ -81,6 +81,7 @@ do_java() { --additional-properties licenseName="Apache 2.0" \ --additional-properties artifactDescription="Client for Manticore Search" \ --additional-properties library="jersey3" \ + --additional-properties failOnUnknownProperties=false \ --additional-properties useJakartaEe=true \ --additional-properties prevVersion=$prev_version \ $build_to_branch diff --git a/templates/Java/libraries/jersey3/JSON.mustache b/templates/Java/libraries/jersey3/JSON.mustache index 97cee639..8ed57947 100755 --- a/templates/Java/libraries/jersey3/JSON.mustache +++ b/templates/Java/libraries/jersey3/JSON.mustache @@ -32,7 +32,7 @@ public class JSON implements ContextResolver { mapper = JsonMapper.builder() .serializationInclusion(JsonInclude.Include.NON_NULL) .configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, {{failOnUnknownProperties}}) .configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, true) .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING) diff --git a/test/java/api/UnknownResponsePropertyTest.java b/test/java/api/UnknownResponsePropertyTest.java new file mode 100644 index 00000000..6202b56e --- /dev/null +++ b/test/java/api/UnknownResponsePropertyTest.java @@ -0,0 +1,47 @@ +package com.manticoresearch.client.api; + +import com.manticoresearch.client.JSON; +import com.manticoresearch.client.model.SearchResponse; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * A response property the client does not know must not be fatal. + * + * Manticore Search adds response fields between releases, and the compatibility table in the + * README promises that an older client stays PARTIALLY compatible with a newer server. That + * promise is only keepable if an unknown property is ignored rather than thrown on: with + * FAIL_ON_UNKNOWN_PROPERTIES enabled, one new field costs the caller the entire response, not + * just the field. + * + * The schema agrees — `searchResponse`, `aggBucketsResult` and `aggBucket` all declare + * `additionalProperties: true` — and the generated Python client honours it. This test holds the + * Java client to the same contract. + */ +public class UnknownResponsePropertyTest { + + @Test + public void anUnknownTopLevelPropertyIsIgnored() throws Exception { + SearchResponse response = JSON.getDefault().getMapper().readValue( + "{\"took\":3,\"timed_out\":false,\"some_field_from_a_newer_server\":42," + + "\"hits\":{\"total\":1,\"total_relation\":\"eq\",\"hits\":[]}}", + SearchResponse.class); + + assertEquals(3, response.getTook()); + assertNotNull(response.getHits()); + assertEquals(1, response.getHits().getTotal()); + } + + @Test + public void anUnknownNestedPropertyIsIgnored() throws Exception { + SearchResponse response = JSON.getDefault().getMapper().readValue( + "{\"took\":0,\"timed_out\":false," + + "\"hits\":{\"total\":0,\"total_relation\":\"eq\",\"hits\":[]}," + + "\"aggregations\":{\"a\":{\"buckets\":[],\"a_future_field\":\"x\"}}}", + SearchResponse.class); + + assertNotNull(response.getAggregations().get("a")); + } +}