Enhanced DynamoDB NPE on putItem with null map value
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 thank you for reaching out. I've run your code sample and I'm seeing two issues:
- When using the StaticTableSchema, the Book object obtained after
getItem()does not contain achaptersmap. That's why calling the DynamoDB Enhanced clientputItem()does not throw any exception, but the new added book item only containsidand the "First" -> null map is lost. - When using the Bean schema, the Book obtained after
getItem()contains bothidandchaptersas expected, but callingputItem()is throwing a null pointer exception.
I'm still investigating what can be the cause.
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
Any progres on that bug?