aws-sdk-java-v2 icon indicating copy to clipboard operation
aws-sdk-java-v2 copied to clipboard

Enhanced DynamoDB NPE on putItem with null map value

Open xstex opened this issue 4 years ago • 3 comments

A null pointer exception is hit in the dynamoDB enhanced client when performing a put of a map attribute with a null value. This happens only when using a beanschema, not a staticschema.

Describe the bug

public class SDKTest {

  public static final StaticTableSchema<Chapter> CHAPTER_SCHEMA = StaticTableSchema.builder(Chapter.class)
    .newItemSupplier(Chapter::new)
    .addAttribute(Integer.class, a -> a.name("page").getter(Chapter::getPage).setter(Chapter::setPage))
    .addAttribute(String.class, a -> a.name("text").getter(Chapter::getText).setter(Chapter::setText))
    .build();

  public static final StaticTableSchema<Book> BOOK_SCHEMA = StaticTableSchema.builder(Book.class)
    .newItemSupplier(Book::new)
    .addAttribute(String.class, a -> a.name("id").tags(primaryPartitionKey()).getter(Book::getId).setter(Book::setId))
    .addAttribute(EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.documentOf(Chapter.class, CHAPTER_SCHEMA)),
      a -> a.name("map").getter(Book::getChapters).setter(Book::setChapters))
    .build();

  @DynamoDbBean
  public static class Book {
    private String id;
    private Map<String, Chapter> chapters;

    public Book() {}
    public Book(String id) { setId(id); }

    @DynamoDbPartitionKey public String getId() {  return id; }
    public void setId(String id) {  this.id = id; }

    public Map<String, Chapter> getChapters() { return chapters; }
    public void setChapters(Map<String, Chapter> chapters) {  this.chapters = chapters;  }
  }

  @DynamoDbBean
  public static class Chapter {
    private Integer page;
    private String text;

    public Integer getPage() {  return page;  }
    public void setPage(Integer page) {  this.page = page;  }

    public String getText() {  return text;  }
    public void setText(String text) {  this.text = text;  }
  }

  public void testStatic(DynamoDbClient client, DynamoDbEnhancedClient enhancedClient) throws Exception {
    DynamoDbTable<Book> table = enhancedClient.table("books", BOOK_SCHEMA);
    table.createTable();

    client.putItem(b -> b.tableName("books")
      .item(new HashMap<String, AttributeValue>() {{
        put("id", AttributeValue.builder().s("123").build());
        put("chapters", AttributeValue.builder()
          .m(Collections.singletonMap("First", AttributeValue.builder().nul(true).build()))
          .build());
      }}));

    Book item = table.getItem(new Book("123"));
    table.putItem(item);
  }

  public void testBean(DynamoDbClient client, DynamoDbEnhancedClient enhancedClient) throws Exception {
    DynamoDbTable<Book> table = enhancedClient.table("books", TableSchema.fromBean(Book.class));
    table.createTable();

    client.putItem(b -> b.tableName("books")
      .item(new HashMap<String, AttributeValue>() {{
        put("id", AttributeValue.builder().s("123").build());
        put("chapters", AttributeValue.builder()
          .m(Collections.singletonMap("First", AttributeValue.builder().nul(true).build()))
          .build());
      }}));

    Book item = table.getItem(new Book("123"));
    table.putItem(item); // <------------------------------LINE THAT THROWS EXCEPTION
  }
}

Behavior

testStatic works, while testBean throws NPE with the following trace:

java.lang.NullPointerException
	at software.amazon.awssdk.enhanced.dynamodb.internal.mapper.ResolvedImmutableAttribute.lambda$create$0(ResolvedImmutableAttribute.java:48)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema.lambda$itemToMap$5(StaticImmutableTableSchema.java:491)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema.itemToMap(StaticImmutableTableSchema.java:489)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.WrappedTableSchema.itemToMap(WrappedTableSchema.java:59)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.WrappedTableSchema.itemToMap(WrappedTableSchema.java:59)
	at software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.DocumentAttributeConverter.transformFrom(DocumentAttributeConverter.java:47)
	at software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.MapAttributeConverter$Delegate.lambda$toAttributeValue$0(MapAttributeConverter.java:165)
	at software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.MapAttributeConverter$Delegate.toAttributeValue(MapAttributeConverter.java:165)
	at software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.MapAttributeConverter.transformFrom(MapAttributeConverter.java:138)
	at software.amazon.awssdk.enhanced.dynamodb.internal.converter.attribute.MapAttributeConverter.transformFrom(MapAttributeConverter.java:76)
	at software.amazon.awssdk.enhanced.dynamodb.internal.mapper.StaticAttributeType.objectToAttributeValue(StaticAttributeType.java:40)
	at software.amazon.awssdk.enhanced.dynamodb.internal.mapper.ResolvedImmutableAttribute.lambda$create$0(ResolvedImmutableAttribute.java:49)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema.lambda$itemToMap$5(StaticImmutableTableSchema.java:491)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.StaticImmutableTableSchema.itemToMap(StaticImmutableTableSchema.java:489)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.WrappedTableSchema.itemToMap(WrappedTableSchema.java:59)
	at software.amazon.awssdk.enhanced.dynamodb.mapper.WrappedTableSchema.itemToMap(WrappedTableSchema.java:59)
	at software.amazon.awssdk.enhanced.dynamodb.internal.operations.PutItemOperation.generateRequest(PutItemOperation.java:71)
	at software.amazon.awssdk.enhanced.dynamodb.internal.operations.PutItemOperation.generateRequest(PutItemOperation.java:40)
	at software.amazon.awssdk.enhanced.dynamodb.internal.operations.CommonOperation.execute(CommonOperation.java:113)
	at software.amazon.awssdk.enhanced.dynamodb.internal.operations.TableOperation.executeOnPrimaryIndex(TableOperation.java:59)
	at software.amazon.awssdk.enhanced.dynamodb.internal.client.DefaultDynamoDbTable.putItem(DefaultDynamoDbTable.java:179)
	at software.amazon.awssdk.enhanced.dynamodb.internal.client.DefaultDynamoDbTable.putItem(DefaultDynamoDbTable.java:187)
	at software.amazon.awssdk.enhanced.dynamodb.internal.client.DefaultDynamoDbTable.putItem(DefaultDynamoDbTable.java:192)
	at com.magicbinder.core.SDKTest.testBean

Your Environment

  • AWS Java SDK version used: 2.15.81
  • JDK version used: java 8
  • Operating System and version: macOs

xstex avatar Feb 15 '21 16:02 xstex

@xstex thank you for reaching out. I've run your code sample and I'm seeing two issues:

  1. When using the StaticTableSchema, the Book object obtained after getItem() does not contain a chapters map. That's why calling the DynamoDB Enhanced client putItem() does not throw any exception, but the new added book item only contains id and the "First" -> null map is lost.
  2. When using the Bean schema, the Book obtained after getItem() contains both id and chapters as expected, but calling putItem() is throwing a null pointer exception.

I'm still investigating what can be the cause.

debora-ito avatar Feb 17 '21 00:02 debora-ito

We currently do have the same issue. Are there any updates on this topic? I think that I found sth. that could have caused the issue. When looking into the Delegate of the MapAttributeConverter, then it seems like that it is not considering that the value could be null in the toAttributeValue() function. I might be able to contribute a fix, but I probably won't find time to work on that topic before Tuesday

svenallers avatar May 21 '21 19:05 svenallers

Any progres on that bug?

matios13 avatar Mar 08 '24 14:03 matios13