Skip to content

TableSchema.mapToItem throws NullPointerException when converting a map AttributeValue containing NUL to a string field #6693

@ayyess

Description

@ayyess

Describe the bug

TableSchema.mapToItem supports converting a map attribute value to a string representation but throws NullPointerException when converting a map AttributeValue containing NUL.

Regression Issue

  • Select this option if this issue appears to be a regression.

Expected Behavior

The deserialized string should contain the string representation of the map including null values, and not throw.

Current Behavior

Throws exception:

java.lang.NullPointerException
	at java.base/java.util.HashMap.merge(HashMap.java:1363)
	at java.base/java.util.stream.Collectors.lambda$toMap$68(Collectors.java:1636)
	at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
	at java.base/java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet.lambda$entryConsumer$0(Collections.java:1779)
	at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
	at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939)
	at java.base/java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntrySetSpliterator.forEachRemaining(Collections.java:1804)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
	at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
	at software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.StringAttributeConverter$Visitor.convertMap(StringAttributeConverter.java:128)
	at software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.StringAttributeConverter$Visitor.convertMap(StringAttributeConverter.java:76)
	at software.amazon.awssdk.enhanced.dynamodb.internal.converter.TypeConvertingVisitor.convert(TypeConvertingVisitor.java:88)
	at software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.EnhancedAttributeValue.convert(EnhancedAttributeValue.java:428)
	at software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.StringAttributeConverter$Visitor.toString(StringAttributeConverter.java:142)
	at software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.StringAttributeConverter.transformTo(StringAttributeConverter.java:73)
	at software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.StringAttributeConverter.transformTo(StringAttributeConverter.java:48)
	at software.amazon.awssdk.enhanced.dynamodb.internal.mapper.StaticAttributeType.attributeValueToObject(StaticAttributeType.java:46)
	at software.amazon.awssdk.enhanced.dynamodb.internal.mapper.ResolvedImmutableAttribute.lambda$create$1(ResolvedImmutableAttribute.java:67)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema.mapToItem(StaticImmutableTableSchema.java:548)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.WrappedTableSchema.mapToItem(WrappedTableSchema.java:62)
	at com.ddbtest.NullAttributeTest.testNullAttributeDeserialization(NullAttributeTest.java:72)

Reproduction Steps

Test to recreate:

package com.ddbtest;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.util.HashMap;
import java.util.Map;
import lombok.Builder;
import lombok.Value;
import org.junit.jupiter.api.Test;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbImmutable;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;

class NullAttributeTest {

  @Value
  @Builder(toBuilder = true)
  @DynamoDbImmutable(builder = ClassWithMap.ClassWithMapBuilder.class)
  public static class ClassWithMap {
    Map<String, String> field;
  }

  @Value
  @Builder(toBuilder = true)
  @DynamoDbImmutable(builder = ClassWithString.ClassWithStringBuilder.class)
  public static class ClassWithString {
    String field;
  }

  @Test
  void testNullAttributeDeserialization() {
    Map<String, AttributeValue> attributesWithANull =
        Map.of(
            "field",
            AttributeValue.builder()
                .m(
                    Map.of(
                        "stringField", AttributeValue.builder().s("value").build(),
                        "nullField", AttributeValue.builder().nul(true).build()))
                .build());

    Map<String, AttributeValue> attributes =
        Map.of(
            "field",
            AttributeValue.builder()
                .m(Map.of("stringField", AttributeValue.builder().s("value").build()))
                .build());

    {
      ClassWithMap result =
          TableSchema.fromClass(ClassWithMap.class).mapToItem(attributesWithANull, false);
      ClassWithMap expected =
          ClassWithMap.builder()
              .field(
                  new HashMap<>() {
                    {
                      put("stringField", "value");
                      put("nullField", null);
                    }
                  })
              .build();
      assertEquals(expected, result);
    }
    {
      ClassWithString result =
          TableSchema.fromClass(ClassWithString.class).mapToItem(attributes, false);
      ClassWithString expected = ClassWithString.builder().field("{stringField=value}").build();
      assertEquals(expected, result);
    }

    assertThrows(
        NullPointerException.class,
        () -> TableSchema.fromClass(ClassWithString.class).mapToItem(attributesWithANull, false));
  }
}

Possible Solution

No response

Additional Information/Context

No response

AWS Java SDK version used

2.0.841071.0

JDK version used

openjdk 21.0.8 2025-07-15 LTS

Operating System and version

macOS 15.7.1

Metadata

Metadata

Assignees

Labels

bugThis issue is a bug.needs-triageThis issue or PR still needs to be triaged.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions