Skip to content

AutoGeneratedTimestampRecordExtension fails on List fields with @DynamoDbConvertedBy when element type is not a DynamoDB-annotated class #6852

@bordax

Description

@bordax

Describe the bug

After this commit that added nested record support to AutoGeneratedTimestampRecordExtension, the extension now recursively
introspects all attributes on a @DynamoDbBean to find @DynamoDbAutoGeneratedTimestampAttribute fields in nested types. This introspection does not respect @DynamoDbConvertedBy on list attributes — it attempts to resolve a TableSchema for the list element type even when a custom converter already handles serialization.

If the list element class is not annotated with @DynamoDbBean or @DynamoDbImmutable, TableSchema.fromClass() throws IllegalArgumentException. This is a regression and these configurations worked before the update.

Workaround:

Add @DynamoDbBean and a no-arg constructor to the nested POJO. This satisfies the schema resolution (the extension finds no timestamp fields and moves on), while the custom converter still handles actual serialization. This works but shouldn't be necessary — the custom converter contract means the SDK should not need to understand the element type's structure.

Impact:
This is a breaking change for any @DynamoDbBean that:

  • Has @DynamoDbAutoGeneratedTimestampAttribute on any field
  • Has a List<T> field with @DynamoDbConvertedBy
  • Where T is not a DynamoDB-annotated class

Regression Issue

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

Expected Behavior

The extension should skip introspection of List<NestedPojo> because the field has a @DynamoDbConvertedBy custom converter. The converter handles serialization — the extension doesn't need to resolve a TableSchema for the element type.

Current Behavior

java.lang.IllegalArgumentException: Class does not appear to be a valid DynamoDb annotated class.                                     
      [class = "class com.example.NestedPojo"]                                                                                          
    at software.amazon.awssdk.enhanced.dynamodb.mapper.TableSchemaFactory.fromClass(TableSchemaFactory.java:39)                         
    at software.amazon.awssdk.enhanced.dynamodb.TableSchema.fromClass(TableSchema.java:204)                                             
  at                                                                                                                                    
  software.amazon.awssdk.enhanced.dynamodb.internal.extensions.utility.NestedRecordUtils.getTableSchemaForListElement(NestedRecordUtils.
  java:88)                                                                                                                              
  at                                                                                                                                    
  software.amazon.awssdk.enhanced.dynamodb.internal.extensions.utility.NestedRecordUtils.getListElementSchemaCached(NestedRecordUtils.ja
  va:230)                                                                                                                               
  at                                                                                                                                    
  software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedTimestampRecordExtension.processTopLevelListOfMapsAttribute(AutoGener
  atedTimestampRecordExtension.java:276)                                                                                                
  at                                                                                                                                    
  software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedTimestampRecordExtension.processTopLevelAttributeForTimestamps(AutoGe
  neratedTimestampRecordExtension.java:223)                                                                                             
  at                                                                                                                                    
  software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedTimestampRecordExtension.lambda$processDirectNestedAttributes$0(AutoG
  eneratedTimestampRecordExtension.java:195)                                                                                            
    ...

Reproduction Steps

  1. Define a @DynamoDbBean entity with @DynamoDbAutoGeneratedTimestampAttribute on a field:
  @DynamoDbBean                                                                                                                         
  public class ParentEntity {                                                                                                           
      private List<NestedPojo> items;                                                                                                   
      private Instant lastUpdated;                                                                                                      
                                                                                                                                        
      @DynamoDbConvertedBy(CustomListConverter.class)                                                                                   
      public List<NestedPojo> getItems() { return items; }                                                                              
                                                                                                                                        
      @DynamoDbAutoGeneratedTimestampAttribute                                                                                          
      public Instant getLastUpdated() { return lastUpdated; }                                                                           
  }    
  1. NestedPojo is a plain POJO with no DynamoDB annotations (serialization is handled entirely by CustomListConverter):
  @Data                                                                                                                                 
  public class NestedPojo {                                                                                                             
      private String name;                                                                                                              
      private int value;                                                                                                                
  }               
  1. Perform any write operation (put, batchWrite) on ParentEntity.

Possible Solution

Root cause:

In NestedRecordUtils.getTableSchemaForListElement(), when EnhancedClientUtils.getNestedSchema() returns Optional.empty(), the fallback unconditionally calls TableSchema.fromClass() on the list element type. It should first check whether the attribute has a custom converter via @DynamoDbConvertedBy and skip introspection if one is present.

Additional Information/Context

No response

AWS Java SDK version used

2.42.31

JDK version used

OpenJDK Runtime Environment Corretto-17.0.18.9.1 (build 17.0.18+9-LTS)

Operating System and version

Amazon Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugThis issue is a bug.investigatingThis issue is being investigated and/or work is in progress to resolve the issue.p1This is a high priority issuepotential-regressionMarking this issue as a potential regression to be checked by team member

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions