Skip to content

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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions