From 11e332c2577eff172a8a909e8e78b7607640a773 Mon Sep 17 00:00:00 2001 From: Radhakrishnan Pachyappan Date: Sun, 5 Jul 2026 14:18:28 +0530 Subject: [PATCH 1/3] Fix ShardProfile.fetch typed as single FetchProfile instead of List OpenSearch returns fetch as a JSON array in profile responses but the spec defined it as a single FetchProfile object. This caused: UnexpectedJsonEventException: Unexpected JSON event 'START_ARRAY' instead of '[START_OBJECT, KEY_NAME]' Change the spec to type: array / items: FetchProfile and update the generated ShardProfile.java to use List with array serialization and arrayDeserializer, matching the pattern already used by aggregations and searches in the same class. Fixes #1965 Signed-off-by: Radhakrishnan Pachyappan --- .../opensearch/core/search/ShardProfile.java | 51 ++++++++++++++----- java-codegen/opensearch-openapi.yaml | 4 +- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/java-client/src/generated/java/org/opensearch/client/opensearch/core/search/ShardProfile.java b/java-client/src/generated/java/org/opensearch/client/opensearch/core/search/ShardProfile.java index 8640b1d2d6..15085ab581 100644 --- a/java-client/src/generated/java/org/opensearch/client/opensearch/core/search/ShardProfile.java +++ b/java-client/src/generated/java/org/opensearch/client/opensearch/core/search/ShardProfile.java @@ -64,8 +64,8 @@ public class ShardProfile implements PlainJsonSerializable, ToCopyableBuilder aggregations; - @Nullable - private final FetchProfile fetch; + @Nonnull + private final List fetch; @Nonnull private final String id; @@ -77,7 +77,7 @@ public class ShardProfile implements PlainJsonSerializable, ToCopyableBuilder aggregations() { /** * API name: {@code fetch} */ - @Nullable - public final FetchProfile fetch() { + @Nonnull + public final List fetch() { return this.fetch; } @@ -136,9 +136,13 @@ protected void serializeInternal(JsonGenerator generator, JsonpMapper mapper) { } generator.writeEnd(); - if (this.fetch != null) { + if (ApiTypeHelper.isDefined(this.fetch)) { generator.writeKey("fetch"); - this.fetch.serialize(generator, mapper); + generator.writeStartArray(); + for (FetchProfile item0 : this.fetch) { + item0.serialize(generator, mapper); + } + generator.writeEnd(); } generator.writeKey("id"); @@ -171,7 +175,7 @@ public static Builder builder() { public static class Builder extends ObjectBuilderBase implements CopyableBuilder { private List aggregations; @Nullable - private FetchProfile fetch; + private List fetch; private String id; private List searches; @@ -179,14 +183,14 @@ public Builder() {} private Builder(ShardProfile o) { this.aggregations = _listCopy(o.aggregations); - this.fetch = o.fetch; + this.fetch = _listCopy(o.fetch); this.id = o.id; this.searches = _listCopy(o.searches); } private Builder(Builder o) { this.aggregations = _listCopy(o.aggregations); - this.fetch = o.fetch; + this.fetch = _listCopy(o.fetch); this.id = o.id; this.searches = _listCopy(o.searches); } @@ -237,15 +241,36 @@ public final Builder aggregations(Function + * Adds all elements of list to fetch. + *

+ */ + @Nonnull + public final Builder fetch(List list) { + this.fetch = _listAddAll(this.fetch, list); + return this; + } + + /** + * API name: {@code fetch} + * + *

+ * Adds one or more values to fetch. + *

*/ @Nonnull - public final Builder fetch(@Nullable FetchProfile value) { - this.fetch = value; + public final Builder fetch(FetchProfile value, FetchProfile... values) { + this.fetch = _listAdd(this.fetch, value, values); return this; } /** * API name: {@code fetch} + * + *

+ * Adds a value to fetch using a builder lambda. + *

*/ @Nonnull public final Builder fetch(Function> fn) { @@ -325,7 +350,7 @@ public ShardProfile build() { protected static void setupShardProfileDeserializer(ObjectDeserializer op) { op.add(Builder::aggregations, JsonpDeserializer.arrayDeserializer(AggregationProfile._DESERIALIZER), "aggregations"); - op.add(Builder::fetch, FetchProfile._DESERIALIZER, "fetch"); + op.add(Builder::fetch, JsonpDeserializer.arrayDeserializer(FetchProfile._DESERIALIZER), "fetch"); op.add(Builder::id, JsonpDeserializer.stringDeserializer(), "id"); op.add(Builder::searches, JsonpDeserializer.arrayDeserializer(SearchProfile._DESERIALIZER), "searches"); } diff --git a/java-codegen/opensearch-openapi.yaml b/java-codegen/opensearch-openapi.yaml index b4829f72a0..468f21a1c0 100644 --- a/java-codegen/opensearch-openapi.yaml +++ b/java-codegen/opensearch-openapi.yaml @@ -52512,7 +52512,9 @@ components: items: $ref: '#/components/schemas/_core.search___SearchProfile' fetch: - $ref: '#/components/schemas/_core.search___FetchProfile' + type: array + items: + $ref: '#/components/schemas/_core.search___FetchProfile' required: - aggregations - id From b2d36b192bce977a3534eb971e796dd6f09b5539 Mon Sep 17 00:00:00 2001 From: Radhakrishnan Pachyappan Date: Sun, 5 Jul 2026 14:20:37 +0530 Subject: [PATCH 2/3] Fix NPE in _listAddAll when server returns JSON null for a list field _listAddAll(list, values) threw NullPointerException via Objects.requireNonNull(values) when the incoming values list was null. This happens when OpenSearch returns JSON null for an optional list field (e.g. ism_template in ISM Policy), causing deserialization to fail with an NPE inside the builder. Guard against null values by returning the existing list unchanged when values is null, consistent with the intent of treating a missing or null list as equivalent to an empty one. Fixes #1813 Signed-off-by: Radhakrishnan Pachyappan --- .../java/org/opensearch/client/util/ObjectBuilderBase.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/java-client/src/main/java/org/opensearch/client/util/ObjectBuilderBase.java b/java-client/src/main/java/org/opensearch/client/util/ObjectBuilderBase.java index e1aa0627c9..524b478148 100644 --- a/java-client/src/main/java/org/opensearch/client/util/ObjectBuilderBase.java +++ b/java-client/src/main/java/org/opensearch/client/util/ObjectBuilderBase.java @@ -95,10 +95,15 @@ protected static List _listAdd(List list, T value, T... values) { /** Add all elements of a list to a (possibly {@code null}) list */ protected static List _listAddAll(List list, List values) { + if (values == null) { + // Server returned JSON null for a list field; treat as empty to avoid NPE. + // See https://github.com/opensearch-project/opensearch-java/issues/1813 + return list; + } if (list == null) { // Keep the original list to avoid an unnecessary copy. // It will be copied if we add more values. - return Objects.requireNonNull(values); + return values; } else { list = _mutableList(list); list.addAll(values); From 2abeeb097e4f9a7ac11f677e2de87890b1d74c84 Mon Sep 17 00:00:00 2001 From: Radhakrishnan Pachyappan Date: Sun, 5 Jul 2026 15:32:16 +0530 Subject: [PATCH 3/3] Add CHANGELOG entry for #2041 Signed-off-by: Radhakrishnan Pachyappan --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ef4423196..ee38b3dd33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Fixed - Fix `unitTest` task not running the tests in the `test` source set ([#2074](https://github.com/opensearch-project/opensearch-java/pull/2074)) - Run model tests against both JSON mappers instead of picking one at random ([#2085](https://github.com/opensearch-project/opensearch-java/pull/2085)) +- Fix `NullPointerException` in `_listAddAll` when server returns JSON `null` for a list field ([#2041](https://github.com/opensearch-project/opensearch-java/pull/2041)) ## [Unreleased 3.x] ### Added