diff --git a/.changes/next-release/bugfix-AmazonDynamoDBEnhancedClient-npe-fixes.json b/.changes/next-release/bugfix-AmazonDynamoDBEnhancedClient-npe-fixes.json new file mode 100644 index 00000000000..88ac605c53a --- /dev/null +++ b/.changes/next-release/bugfix-AmazonDynamoDBEnhancedClient-npe-fixes.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "Amazon DynamoDB Enhanced Client", + "contributor": "", + "description": "Fix NullPointerException in `EnhancedType.hashCode()`, `EnhancedType.equals()`, and `EnhancedType.toString()` when using wildcard types." +} diff --git a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/EnhancedType.java b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/EnhancedType.java index e220854247b..478bed5f491 100644 --- a/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/EnhancedType.java +++ b/services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/EnhancedType.java @@ -541,14 +541,17 @@ private List> loadTypeParameters(Type type) { private StringBuilder innerToString() { StringBuilder result = new StringBuilder(); - result.append(rawClass.getTypeName()); + if (isWildcard) { + result.append("?"); + } else { + result.append(rawClass.getTypeName()); - if (null != rawClassParameters && !rawClassParameters.isEmpty()) { - result.append("<"); - result.append(rawClassParameters.stream().map(EnhancedType::innerToString).collect(Collectors.joining(", "))); - result.append(">"); + if (null != rawClassParameters && !rawClassParameters.isEmpty()) { + result.append("<"); + result.append(rawClassParameters.stream().map(EnhancedType::innerToString).collect(Collectors.joining(", "))); + result.append(">"); + } } - return result; } @@ -566,7 +569,7 @@ public boolean equals(Object o) { if (isWildcard != enhancedType.isWildcard) { return false; } - if (!rawClass.equals(enhancedType.rawClass)) { + if (rawClass != null ? !rawClass.equals(enhancedType.rawClass) : enhancedType.rawClass != null) { return false; } if (rawClassParameters != null ? !rawClassParameters.equals(enhancedType.rawClassParameters) : @@ -584,7 +587,7 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = (isWildcard ? 1 : 0); - result = 31 * result + rawClass.hashCode(); + result = 31 * result + (rawClass != null ? rawClass.hashCode() : 0); result = 31 * result + (rawClassParameters != null ? rawClassParameters.hashCode() : 0); result = 31 * result + (tableSchema != null ? tableSchema.hashCode() : 0); result = 31 * result + (documentConfiguration != null ? documentConfiguration.hashCode() : 0); diff --git a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/EnhancedTypeTest.java b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/EnhancedTypeTest.java index a8732a15aba..6d91bc1885f 100644 --- a/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/EnhancedTypeTest.java +++ b/services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/EnhancedTypeTest.java @@ -239,6 +239,45 @@ public void documentOf_withEnhancedTypeConfiguration() { assertThat(type.documentConfiguration().get().preserveEmptyObject()).isTrue(); } + @Test + public void wildcardType_hashCode_doesNotRaiseNPE() { + // When using a wildcard type like List, the type parameter is a wildcard + // with rawClass = null. hashCode() should handle this without NPE. + EnhancedType> listWithWildcard = new EnhancedType>(){}; + EnhancedType wildcardParam = listWithWildcard.rawClassParameters().get(0); + + // This should not throw NPE + assertThatCode(() -> wildcardParam.hashCode()).doesNotThrowAnyException(); + } + + @Test + public void wildcardType_equals_handlesNullRawClass() { + // Wildcard types should be comparable via equals without NPE + EnhancedType> listWithWildcard1 = new EnhancedType>(){}; + EnhancedType> listWithWildcard2 = new EnhancedType>(){}; + + EnhancedType wildcard1 = listWithWildcard1.rawClassParameters().get(0); + EnhancedType wildcard2 = listWithWildcard2.rawClassParameters().get(0); + + // Wildcards should be equal to each other + assertThat(wildcard1).isEqualTo(wildcard2); + assertThat(wildcard1.hashCode()).isEqualTo(wildcard2.hashCode()); + } + + @Test + public void wildcardType_toString_doesNotRaiseNPE() { + // Wildcard types should be convertible to string without NPE + EnhancedType> listWithWildcard = new EnhancedType>(){}; + EnhancedType wildcardParam = listWithWildcard.rawClassParameters().get(0); + + // This should not throw NPE and should return "?" for wildcard + assertThatCode(() -> wildcardParam.toString()).doesNotThrowAnyException(); + assertThat(wildcardParam.toString()).isEqualTo("EnhancedType(?)"); + + // Also verify that the parent type renders correctly with wildcard + assertThat(listWithWildcard.toString()).isEqualTo("EnhancedType(java.util.List)"); + } + public class InnerType { }