Added support for DynamoDbAutoGeneratedKey annotation
Description
Added the facility of using an annotation that will auto-generate a key for an attribute in a class,
similar to the legacy V1 @DynamoDBAutoGeneratedKey, now ported and adapted for V2 with the Enhanced Client.
Important Restrictions
This annotation is only valid for primary keys (PK/SK) or secondary index keys (GSI/LSI PK/SK).
If applied to a non-key attribute, the extension will throw an IllegalArgumentException.
Conflict Prevention: This annotation cannot be used together with @DynamoDbAutoGeneratedUuid on the same attribute. If both annotations are applied to the same field, an IllegalArgumentException will be thrown at runtime to prevent unpredictable behavior based on extension load order.
If the attribute is not provided by the client, the SDK will automatically generate a value (UUID by default)
during serialization. Unlike @DynamoDbAutoGeneratedUuid, this extension only generates UUIDs when the attribute value is null or empty, preserving existing values.
UpdateBehavior Limitations
Primary Keys: @DynamoDbUpdateBehavior has no effect on primary partition keys or primary sort keys. Primary keys are required for UpdateItem operations in DynamoDB and cannot be conditionally updated. UUIDs will be generated whenever the primary key attribute is missing or empty, regardless of any UpdateBehavior setting.
Secondary Index Keys: For GSI/LSI keys, @DynamoDbUpdateBehavior can be used to control generation:
-
Default (
WRITE_ALWAYS) → The attribute will be regenerated on every write if missing, even during updates. Useful for attributes likelastUpdatedKeythat are meant to refresh often. -
WRITE_IF_NOT_EXISTS→ The attribute will only be generated once (on the first insert) and preserved across updates. This is the recommended option for stable identifiers likecreatedKey.
Motivation and Context
This functionality was present in V1 under @DynamoDBAutoGeneratedKey. Many users requested its reintroduction in V2.
By introducing this annotation and the supporting extension, we align the Enhanced Client API with developer expectations and provide a familiar migration path.
Modifications
- Added the new annotation
@DynamoDbAutoGeneratedKeyin thesoftware.amazon.awssdk.enhanced.dynamodb.extensions.annotationspackage. - Introduced
AutoGeneratedKeyExtension, which ensures attributes annotated with@DynamoDbAutoGeneratedKeyare populated with a UUID when absent. This usesUUID.randomUUID()under the hood. - Added
AutoGeneratedKeyTagas the annotation tag integration point. -
Conflict Detection: Added bidirectional conflict detection between
@DynamoDbAutoGeneratedKeyand@DynamoDbAutoGeneratedUuidto prevent unpredictable behavior. -
UpdateBehavior Clarification: Updated documentation to clearly explain that
@DynamoDbUpdateBehavioronly affects secondary index keys, not primary keys. - Integrated with
@DynamoDbUpdateBehaviorto support both stable one-time keys - works for secondary index keys only (WRITE_IF_NOT_EXISTS) and regenerating keys (WRITE_ALWAYS). - Validated attribute type constraints (only
Stringis supported). - Enforced placement rules (only PK/SK, LSI/GSI keys are allowed).
- Added corresponding unit tests (
AutoGeneratedKeyExtensionTest) and functional integration tests (AutoGeneratedKeyRecordTest) to verify correct behavior with real DynamoDB operations. -
Conflict Testing: Added comprehensive tests in both
AutoGeneratedKeyRecordTestandAutoGeneratedUuidRecordTestto verify exception throwing when conflicting annotations are used. - Tests validate multiple scenarios, including updates, conditional writes, composite annotations, conflict detection, and integration with
VersionedRecordExtension.
Testing
The changes have already been tested by running the existing tests and also added new unit/integration tests
for the new flow, ensuring parity with V1 behavior while also validating V2-specific integration points and conflict prevention mechanisms.
Test Coverage Checklist
| Scenario | Done | Comments if Not Done |
|---|---|---|
| 1. Different TableSchema Creation Methods | ||
| a. TableSchema.fromBean(Customer.class) | [x] | |
| b. TableSchema.fromImmutableClass(Customer.class) for immutable classes | [x] | |
| c. TableSchema.documentSchemaBuilder().build() | [ ] | |
| d. StaticTableSchema.builder(Customer.class) | [x] | |
| 2. Nesting of Different TableSchema Types | ||
| a. @DynamoDbBean with annotated auto-generated key | [x] | |
| b. @DynamoDbImmutable with annotated auto-generated key | [x] | |
| c. Auto-generated key combined with partition/sort key | [x] | |
| 3. CRUD Operations | ||
| a. scan() | [ ] | |
| b. query() | [x] | |
| c. updateItem() preserves existing value or generates when absent | [x] | |
| d. putItem() with no key set (auto-generation occurs) | [x] | |
| e. putItem() with key set manually | [x] | |
| f. getItem() retrieves auto-generated key | [x] | |
| g. deleteItem() | [ ] | |
| h. batchGetItem() | [ ] | |
| i. batchWriteItem() | [ ] | |
| j. transactGetItems() | [ ] | |
| k. transactWriteItems() | [ ] | |
| 4. Data Types and Null Handling | ||
| a. Annotated attribute is null → key auto-generated | [x] | |
| b. Annotated attribute non-null → value preserved | [x] | |
| c. Validation rejects non-String annotated attribute | [x] | |
| 5. AsyncTable and SyncTable | ||
| a. DynamoDbAsyncTable Testing | [x] | |
| b. DynamoDbTable Testing | [x] | |
| 6. New/Modification in Extensions | ||
| a. Works with other extensions like VersionedRecordExtension | [x] | |
| b. Test with Default Values in Annotations | [ ] | |
| c. Combination of Annotation and Builder passes extension | [ ] | |
| 7. New/Modification in Converters | ||
| a. Tables with Scenario in ScenarioSl No.1 (All table schemas are Must) | [ ] | |
| b. Test with Default Values in Annotations | [ ] | |
| c. Test All Scenarios from 1 to 5 | [ ] |
Types of changes
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
Checklist
- [x] I have read the CONTRIBUTING document
- [x] Local run of
mvn installsucceeds - [x] My code follows the code style of this project
- [ ] My change requires a change to the Javadoc documentation
- [ ] I have updated the Javadoc documentation accordingly
- [x] I have added tests to cover my changes
- [x] All new and existing tests passed
- [x] I have added a changelog entry. Adding a new entry must be accomplished by running the
scripts/new-changescript and following the instructions. Commit the new file created by the script in.changes/next-releasewith your changes. - [ ] My change is to implement 1.11 parity feature and I have updated LaunchChangelog
License
- [x] I confirm that this pull request can be released under the Apache 2 license
This solution still doesn't work for PartitionKey or SortKey, because UpdateBehavior are not applied to either as shown in the print below.
This is a workaround because it doesn't generate new values in primaryKeys because the new extension validates if the value is missing, but it doesn't solve the root problem. Therefore, each new extension that can be applied to a PK must address this issue. The best approach is to not pass PKs to WriteModification.
Hello @marcusvoltolim,
Thank you very much for your review.
I have added tests for @DynamoDbSortKey as you suggested. The test class (AutoGeneratedKeyRecordTest) now covers all 4 supported key types with @DynamoDbAutoGeneratedKey:
- Primary partition key (
@DynamoDbPartitionKey) - Primary sort key (
@DynamoDbSortKey) - GSI partition key (
@DynamoDbSecondaryPartitionKey) - GSI sort key (
@DynamoDbSecondarySortKey)
Regarding your comment about @DynamoDbUpdateBehavior: You're absolutely correct. Based on the implementation, the @DynamoDbAutoGeneratedKey annotation works with @DynamoDbUpdateBehavior only for secondary index keys (GSI/LSI partition and sort keys). For primary keys (both partition and sort), UpdateBehavior has no effect since primary keys cannot be null in DynamoDB and are always required for update operations.
I have updated the tests, ticket description, and PR description to clearly reflect this.
Could you please take another look when you have a chance? Thank you!
Hi @anasatirbasa ,
I read through the PR, I have a few concerns I wanted to raise with the entire team before providing feedback. Will update you soon.
Hi @anasatirbasa, thanks for the wait.
I have reviewed this PR with the team and have a few points that we would ideally like to fix.
I agree that introducing this separate annotation would solve the problem in an "opt in" non invasive way.
Considerations:
- I'm trying to verify is whether the following is correct:
If both @DynamoDbAutoGeneratedUuid and @DynamoDbAutoGeneratedKey are applied to the same field, both extensions will execute in chain order. Since AutoGeneratedKeyExtension checks for existing values before generating, the behavior depends on registration order:
- If
AutoGeneratedUuidExtension(current) runs first: It generates a UUID, thenAutoGeneratedKeyExtension(new) sees the value exists and skips generation - If
AutoGeneratedKeyExtension(current) runs first: It generates conditionally, thenAutoGeneratedUuidExtension(old) overwrites it anyway
This creates unpredictable behavior. If this is the case, can we please add some tests that cover it, and throw an exception when both annotations (new and current) are applied to the same field?
- Fix documentation for WRITE_IF_NOT_EXISTS:
In v1, @DynamoDBAutoGeneratedKey used DynamoDBAutoGenerateStrategy.CREATE, which only generated UUIDs when the annotated value was null.
The v2 Enhanced Client’s @DynamoDbAutoGeneratedUuid removed this conditional logic entirely - it always generates regardless of existing values. The javadoc documents this as intentional:
“Every time a record with this attribute is written to the database it will update the attribute with a UUID#randomUUID string.”
but also, misleadingly suggests using UpdateBehavior.WRITE_IF_NOT_EXISTS as a workaround, which doesn’t work for primary keys due to DynamoDB’s updateItem API not allowing conditional updates on primary key attributes.
We will likely ask you to add more test coverage similar to other extensions test coverage, but I will provide more concrete about testing gaps in the near future after another review.
Thanks, Ran~
Please refer to my comments on the PR.
Hello, @RanVaknin! Thank you very much for your feedback! I am in progress with the changes.
Hello @RanVaknin,
Thank you very much for the feedback! I've addressed all three points you raised:
1. Documentation Updates for UpdateBehavior.WRITE_IF_NOT_EXISTS
I've updated the documentation in both @DynamoDbAutoGeneratedKey annotation and AutoGeneratedKeyExtension class to clearly explain:
-
Primary Keys:
@DynamoDbUpdateBehaviorhas no effect on primary partition keys or primary sort keys due to DynamoDB's UpdateItem API limitations -
Secondary Index Keys:
UpdateBehavioronly works for GSI/LSI keys, whereWRITE_IF_NOT_EXISTSgenerates UUIDs only on first write andWRITE_ALWAYSregenerates on every write
2. Conflicting Annotations Prevention
Your assumption about unpredictable behavior based on extension load order was correct. I've implemented bidirectional conflict detection:
-
AutoGeneratedKeyExtension: Checks for existing
@DynamoDbAutoGeneratedUuidannotations and throwsIllegalArgumentExceptionif found on the same attribute -
AutoGeneratedUuidExtension: Added inverse logic to check for existing
@DynamoDbAutoGeneratedKeyannotations and throws the same exception
Both extensions now prevent conflicting annotations regardless of load order, ensuring predictable behavior.
3. Test Coverage for Conflicting Behavior
I've added tests across multiple test classes:
AutoGeneratedKeyExtensionTest:
-
conflictingAnnotations_throwsIllegalArgumentException()- Tests conflict on primary key -
conflictingAnnotations_onSecondaryKey_throwsIllegalArgumentException()- Tests conflict on GSI key -
conflictDetection_worksRegardlessOfExtensionOrder()- Verifies detection works both ways
AutoGeneratedUuidExtensionTest:
-
conflictingAnnotations_throwsIllegalArgumentException()- Tests conflict detection from UUID extension side
AutoGeneratedKeyRecordTest (functional):
-
conflictingAnnotations_throwsException()- Tests conflict using bean annotations with both extensions loaded
AutoGeneratedUuidRecordTest (functional):
-
conflictingAnnotations_throwsException()- Tests conflict from UUID extension perspective with both extensions loaded
ConflictingAnnotationsTest (dedicated):
-
keyExtensionFirst_detectsConflictWithUuidExtension()- Tests when Key extension runs first -
uuidExtensionFirst_detectsConflictWithKeyExtension()- Tests when UUID extension runs first -
separateAttributes_noConflict()- Verifies no conflict when annotations are on different attributes
All tests verify that IllegalArgumentException is thrown with messages explaining the conflicting behaviors, preventing the unpredictable results you identified.
Hi @anasatirbasa,
Thanks for the follow up. I'll review and get back to you asap.
Also a request for the future; please do not squash the commits, it makes it difficult to review the particular changes requested :)
Thanks 🙏 Ran~
HI @anasatirbasa
Can you fix the javadoc here? (this is incorrect javadoc that we already have in the SDK) :
* Every time a new record is successfully put into the database, the specified attribute will be automatically populated with a
* unique UUID generated using {@link java.util.UUID#randomUUID()}. If the UUID needs to be created only for `putItem` and should
* not be generated for an `updateItem`, then
* {@link software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior#WRITE_IF_NOT_EXISTS} must be along with
* {@link DynamoDbUpdateBehavior}
We want to clarify that WRITE_IF_NOT_EXISTS doesn't apply to primary keys.
The rest of the PR looks good. Can we add functional tests for TransactWriteItems or BatchWriteItems?
Thanks, Ran~
Hello @RanVaknin,
Thank you for your review! I have updated the Javadoc and added tests for TransactWriteItems and BatchWriteItems in AutoGeneratedKeyRecordTest.java.