From 39456ce6053f2309438b868338911c8f59ef7854 Mon Sep 17 00:00:00 2001
From: RanVaknin <50976344+RanVaknin@users.noreply.github.com>
Date: Thu, 19 Mar 2026 00:14:16 -0700
Subject: [PATCH 1/5] Skip null non required fields in
JsonProtocolMarshaller.doMarshall
---
.../json/internal/marshall/JsonProtocolMarshaller.java | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshaller.java b/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshaller.java
index eaa6c2ef5cd5..57db62d81e73 100644
--- a/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshaller.java
+++ b/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshaller.java
@@ -35,6 +35,7 @@
import software.amazon.awssdk.core.protocol.MarshallLocation;
import software.amazon.awssdk.core.protocol.MarshallingType;
import software.amazon.awssdk.core.traits.PayloadTrait;
+import software.amazon.awssdk.core.traits.RequiredTrait;
import software.amazon.awssdk.core.traits.TimestampFormatTrait;
import software.amazon.awssdk.core.traits.TraitType;
import software.amazon.awssdk.http.SdkHttpFullRequest;
@@ -212,8 +213,13 @@ void doMarshall(SdkPojo pojo) {
}
} else if (isExplicitPayloadMember(field)) {
marshallExplicitJsonPayload(field, val);
- } else {
+ } else if (val != null) {
marshallField(field, val);
+ } else if (field.containsTrait(RequiredTrait.class,
+ TraitType.REQUIRED_TRAIT)) {
+ throw new IllegalArgumentException(
+ String.format("Parameter '%s' must not be null",
+ field.locationName()));
}
}
}
From 1626e1b4ce2620628089716b0077d58c4449d89d Mon Sep 17 00:00:00 2001
From: RanVaknin <50976344+RanVaknin@users.noreply.github.com>
Date: Fri, 20 Mar 2026 10:29:46 -0700
Subject: [PATCH 2/5] Fix domarshall logic
---
.../internal/marshall/JsonProtocolMarshaller.java | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshaller.java b/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshaller.java
index 57db62d81e73..7bf7738a8563 100644
--- a/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshaller.java
+++ b/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshaller.java
@@ -215,11 +215,14 @@ void doMarshall(SdkPojo pojo) {
marshallExplicitJsonPayload(field, val);
} else if (val != null) {
marshallField(field, val);
- } else if (field.containsTrait(RequiredTrait.class,
- TraitType.REQUIRED_TRAIT)) {
- throw new IllegalArgumentException(
- String.format("Parameter '%s' must not be null",
- field.locationName()));
+ } else if (field.location() == MarshallLocation.PAYLOAD) {
+ if (field.containsTrait(RequiredTrait.class, TraitType.REQUIRED_TRAIT)) {
+ throw new IllegalArgumentException(
+ String.format("Parameter '%s' must not be null",
+ field.locationName()));
+ }
+ } else {
+ marshallField(field, val);
}
}
}
From f9e4340b2546405d6f9c9dfeba4c73af5c153b40 Mon Sep 17 00:00:00 2001
From: RanVaknin <50976344+RanVaknin@users.noreply.github.com>
Date: Fri, 20 Mar 2026 12:42:27 -0700
Subject: [PATCH 3/5] Fix logic, add null suppression
---
.../amazon/awssdk/spotbugs-suppressions.xml | 8 ++++++++
.../internal/marshall/JsonProtocolMarshaller.java | 13 ++++++-------
2 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml b/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml
index 6871e760a793..16c9254404a1 100644
--- a/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml
+++ b/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml
@@ -523,4 +523,12 @@
+
+
+
+
+
+
+
diff --git a/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshaller.java b/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshaller.java
index 7bf7738a8563..427ae3779b13 100644
--- a/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshaller.java
+++ b/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshaller.java
@@ -215,14 +215,13 @@ void doMarshall(SdkPojo pojo) {
marshallExplicitJsonPayload(field, val);
} else if (val != null) {
marshallField(field, val);
- } else if (field.location() == MarshallLocation.PAYLOAD) {
- if (field.containsTrait(RequiredTrait.class, TraitType.REQUIRED_TRAIT)) {
- throw new IllegalArgumentException(
- String.format("Parameter '%s' must not be null",
- field.locationName()));
- }
- } else {
+ } else if (field.location() != MarshallLocation.PAYLOAD) {
marshallField(field, val);
+ } else if (field.containsTrait(RequiredTrait.class,
+ TraitType.REQUIRED_TRAIT)) {
+ throw new IllegalArgumentException(
+ String.format("Parameter '%s' must not be null",
+ field.locationName()));
}
}
}
From 01e27392d29e1510df90b305b86864b0b33ce137 Mon Sep 17 00:00:00 2001
From: RanVaknin <50976344+RanVaknin@users.noreply.github.com>
Date: Sun, 22 Mar 2026 16:50:11 -0700
Subject: [PATCH 4/5] Add test coverage
---
.../amazon/awssdk/spotbugs-suppressions.xml | 2 +-
.../marshall/JsonProtocolMarshaller.java | 6 +-
.../marshall/JsonProtocolMarshallerTest.java | 197 ++++++++++++++++++
3 files changed, 200 insertions(+), 5 deletions(-)
create mode 100644 core/protocols/aws-json-protocol/src/test/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshallerTest.java
diff --git a/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml b/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml
index 16c9254404a1..69a7894bfe94 100644
--- a/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml
+++ b/build-tools/src/main/resources/software/amazon/awssdk/spotbugs-suppressions.xml
@@ -525,7 +525,7 @@
+ whose NULL marshallers handle null validation. -->
diff --git a/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshaller.java b/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshaller.java
index 427ae3779b13..89ad22e6a54c 100644
--- a/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshaller.java
+++ b/core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshaller.java
@@ -217,11 +217,9 @@ void doMarshall(SdkPojo pojo) {
marshallField(field, val);
} else if (field.location() != MarshallLocation.PAYLOAD) {
marshallField(field, val);
- } else if (field.containsTrait(RequiredTrait.class,
- TraitType.REQUIRED_TRAIT)) {
+ } else if (field.containsTrait(RequiredTrait.class, TraitType.REQUIRED_TRAIT)) {
throw new IllegalArgumentException(
- String.format("Parameter '%s' must not be null",
- field.locationName()));
+ String.format("Parameter '%s' must not be null", field.locationName()));
}
}
}
diff --git a/core/protocols/aws-json-protocol/src/test/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshallerTest.java b/core/protocols/aws-json-protocol/src/test/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshallerTest.java
new file mode 100644
index 000000000000..58190503beca
--- /dev/null
+++ b/core/protocols/aws-json-protocol/src/test/java/software/amazon/awssdk/protocols/json/internal/marshall/JsonProtocolMarshallerTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.protocols.json.internal.marshall;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.core.SdkField;
+import software.amazon.awssdk.core.SdkPojo;
+import software.amazon.awssdk.core.protocol.MarshallLocation;
+import software.amazon.awssdk.core.protocol.MarshallingType;
+import software.amazon.awssdk.core.traits.LocationTrait;
+import software.amazon.awssdk.core.traits.RequiredTrait;
+import software.amazon.awssdk.http.SdkHttpFullRequest;
+import software.amazon.awssdk.http.SdkHttpMethod;
+import software.amazon.awssdk.protocols.core.OperationInfo;
+import software.amazon.awssdk.protocols.core.ProtocolMarshaller;
+import software.amazon.awssdk.protocols.json.AwsJsonProtocol;
+import software.amazon.awssdk.protocols.json.AwsJsonProtocolMetadata;
+import software.amazon.awssdk.protocols.json.internal.AwsStructuredPlainJsonFactory;
+
+class JsonProtocolMarshallerTest {
+
+ private static final URI ENDPOINT = URI.create("http://localhost");
+ private static final String CONTENT_TYPE = "application/x-amz-json-1.0";
+ private static final OperationInfo OP_INFO = OperationInfo.builder()
+ .httpMethod(SdkHttpMethod.POST)
+ .hasImplicitPayloadMembers(true)
+ .build();
+ private static final AwsJsonProtocolMetadata METADATA =
+ AwsJsonProtocolMetadata.builder()
+ .protocol(AwsJsonProtocol.AWS_JSON)
+ .contentType(CONTENT_TYPE)
+ .build();
+
+ @Test
+ void nullPayloadField_notRequired_isSkipped() {
+ SdkField field = payloadField("OptionalField", obj -> null);
+ SdkPojo pojo = new SimplePojo(field);
+
+ SdkHttpFullRequest result = createMarshaller().marshall(pojo);
+
+ String body = bodyAsString(result);
+ assertThat(body).doesNotContain("OptionalField");
+ }
+
+ @Test
+ void nullPayloadField_required_throwsIllegalArgumentException() {
+ SdkField field = SdkField.builder(MarshallingType.STRING)
+ .memberName("RequiredField")
+ .getter(obj -> null)
+ .setter((obj, val) -> { })
+ .traits(LocationTrait.builder()
+ .location(MarshallLocation.PAYLOAD)
+ .locationName("RequiredField")
+ .build(),
+ RequiredTrait.create())
+ .build();
+
+ SdkPojo pojo = new SimplePojo(field);
+
+ assertThatThrownBy(() -> createMarshaller().marshall(pojo))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("RequiredField")
+ .hasMessageContaining("must not be null");
+ }
+
+ @Test
+ void nonNullPayloadField_isSerialized() {
+ SdkField field = payloadField("Name", obj -> "hello");
+ SdkPojo pojo = new SimplePojo(field);
+
+ SdkHttpFullRequest result = createMarshaller().marshall(pojo);
+
+ String body = bodyAsString(result);
+ assertThat(body).contains("\"Name\"");
+ assertThat(body).contains("\"hello\"");
+ }
+
+ @Test
+ void nullNonPayloadField_stillGoesToMarshallField() {
+ SdkField field = SdkField.builder(MarshallingType.STRING)
+ .memberName("HeaderField")
+ .getter(obj -> null)
+ .setter((obj, val) -> { })
+ .traits(LocationTrait.builder()
+ .location(MarshallLocation.HEADER)
+ .locationName("x-custom-header")
+ .build())
+ .build();
+
+ SdkPojo pojo = new SimplePojo(field);
+
+ assertThatNoException().isThrownBy(
+ () -> createMarshaller().marshall(pojo));
+ }
+
+ @Test
+ void nullPathField_notRequired_stillThrows() {
+ SdkField field = SdkField.builder(MarshallingType.STRING)
+ .memberName("PathParam")
+ .getter(obj -> null)
+ .setter((obj, val) -> { })
+ .traits(LocationTrait.builder()
+ .location(MarshallLocation.PATH)
+ .locationName("PathParam")
+ .build())
+ .build();
+
+ SdkPojo pojo = new SimplePojo(field);
+
+ assertThatThrownBy(() -> createMarshaller().marshall(pojo))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("PathParam")
+ .hasMessageContaining("must not be null");
+ }
+
+ private static SdkField payloadField(String name,
+ java.util.function.Function