From f8084fd51098801ed15320b900db68849c6f8393 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 9c36a763fb6d21d9d3f65d7d55639a616e339386 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 dc9939a82a95b164948a4f225fefc217cfd734f4 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 f85eaecf78..75a4cdc859 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Add document lifecycle guide and runnable sample ([#2017](https://github.com/opensearch-project/opensearch-java/pull/2017)) ### Fixed +- 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