Added support for DynamoDbAutoGeneratedKey annotation#6373
Added support for DynamoDbAutoGeneratedKey annotation#6373anasatirbasa wants to merge 26 commits intoaws:masterfrom
Conversation
519acfe to
fa35bfc
Compare
586dbf5 to
1c0b19f
Compare
335e532 to
f3a3ad1
Compare
fb82197 to
ede74c8
Compare
marcusvoltolim
left a comment
There was a problem hiding this comment.
No tests with @DynamoDbSortKey
...ava/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
Outdated
Show resolved
Hide resolved
...ava/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
Outdated
Show resolved
Hide resolved
...ava/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedKeyRecordTest.java
Show resolved
Hide resolved
...main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
Outdated
Show resolved
Hide resolved
|
Hello @marcusvoltolim, Thank you very much for your review. I have added tests for
Regarding your comment about 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:
If both
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?
In v1,
but also, misleadingly suggests using 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, |
RanVaknin
left a comment
There was a problem hiding this comment.
Please refer to my comments on the PR.
...main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
Outdated
Show resolved
Hide resolved
Hello, @RanVaknin! Thank you very much for your feedback! I am in progress with the changes. |
3ff8f87 to
7cec4a2
Compare
|
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_EXISTSI've updated the documentation in both
2. Conflicting Annotations PreventionYour assumption about unpredictable behavior based on extension load order was correct. I've implemented bidirectional conflict detection:
Both extensions now prevent conflicting annotations regardless of load order, ensuring predictable behavior. 3. Test Coverage for Conflicting BehaviorI've added tests across multiple test classes: AutoGeneratedKeyExtensionTest:
AutoGeneratedUuidExtensionTest:
AutoGeneratedKeyRecordTest (functional):
AutoGeneratedUuidRecordTest (functional):
ConflictingAnnotationsTest (dedicated):
All tests verify that |
|
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 🙏 |
|
Can you fix the javadoc here? (this is incorrect javadoc that we already have in the SDK) : We want to clarify that The rest of the PR looks good. Can we add functional tests for Thanks, |
shetsa-amzn
left a comment
There was a problem hiding this comment.
The name @DynamoDbAutoGeneratedKey implies it generates "keys" generically, but it only generates UUIDs. The V1 annotation had the same name, so there's a migration parity argument. However, in V2 we already have @DynamoDbAutoGeneratedUuid which does the same thing (generate UUIDs) but with different semantics (always vs. only-when-missing).
The real behavioral difference is "generate only when absent" vs "always regenerate." A name like @DynamoDbAutoGeneratedKeyUuid or documenting this distinction more prominently would reduce confusion. Users will inevitably ask: "which UUID annotation should I use?"
shetsa-amzn
left a comment
There was a problem hiding this comment.
Do we have test scenarios for the batch and transaction operations are particularly important because the extension's beforeWrite() hook behavior with batch operations could have subtle differences?
...main/java/software/amazon/awssdk/enhanced/dynamodb/extensions/AutoGeneratedKeyExtension.java
Outdated
Show resolved
Hide resolved
shetsa-amzn
left a comment
There was a problem hiding this comment.
The existing AutoGeneratedUuidExtension exposes a static create() factory method. This PR only provides builder().build(). Should keep this parity?
shetsa-amzn
left a comment
There was a problem hiding this comment.
Key placement validation should ideally happen during modifyMetadata() or at schema construction time, not deferred to runtime writes. I understand the StaticAttributeTag interface doesn't expose table metadata at that point, but this is worth calling out as a design limitation
Hello @shetsa-amzn, I have introduced a static create() factory method to ensure API parity. |
Hello @shetsa-amzn, Yes, we have test scenarios for batch / transaction operations. For AutogeneratedKey:1. Unit tests - In AutoGeneratedKeyRecordTest:
2. Functional tests - In AutoGeneratedKeyExtensionTest:
For AutogeneratedUuid:Functional tests - In AutoGeneratedUuidExtensionTest:
|
Hello @shetsa-amzn, We intentionally kept the annotation name to maintain parity with the V1 SDK. Since @DynamoDbAutoGeneratedKey existed in V1 with the same name and behavior, preserving it in V2 ensures a clear migration and avoids breaking changes for customers upgrading from V1. 1. @DynamoDbAutoGeneratedUuid: 2. @DynamoDbAutoGeneratedKey: /**
* Marks an attribute to be automatically populated with a new UUID on every write operation.
*
* <p><b>Intended Usage:</b>
* This annotation is generic and may be applied to any {@link String} attribute, not only key attributes.
*
* <h3>Generation Semantics:</h3>
* On every write (put, update, batch write, or transaction write), the attribute is replaced with
* a new value generated using {@link java.util.UUID#randomUUID()}, regardless of any existing value.
*
* <p><b>Difference from {@code @DynamoDbAutoGeneratedKey}:</b>
* {@code @DynamoDbAutoGeneratedKey} is intended for key attributes and generates UUID only when the
* value is absent. {@code @DynamoDbAutoGeneratedUuid} annotation always regenerates the UUID on each write.</p>
*
* <h3>Type Restriction:</h3>
* This annotation may only be applied to attributes of type {@link String}.
*/
@SdkPublicApi
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@BeanTableSchemaAttributeTag(AutoGeneratedUuidTag.class)
public @interface DynamoDbAutoGeneratedUuid {
}/**
* Marks a key attribute to be automatically populated with a UUID when the value
* is null or empty during a write operation.
*
* <p><b>Usage:</b> May only be applied to key attributes: primary partition key,
* primary sort key, or secondary index (GSI/LSI) keys. If used on a non-key attribute,
* {@code AutoGeneratedKeyExtension} throws an {@link IllegalArgumentException} during schema validation.
*
* <p><b>Semantics:</b> Generates a UUID using {@link java.util.UUID#randomUUID()}
* only when the attribute is absent. Existing values are preserved.
*
* <p><b>Difference from {@code @DynamoDbAutoGeneratedUuid}:</b>
* This annotation is intended for key attributes and generates a value only when missing. {@code @DynamoDbAutoGeneratedUuid} can
* be applied to any attribute and always generates a new UUID on every write.
*
* <p>Valid only for {@link String} attributes.
*/
@SdkPublicApi
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
@BeanTableSchemaAttributeTag(AutoGeneratedKeyTag.class)
public @interface DynamoDbAutoGeneratedKey {
} |
Hello @shetsa-amzn, You’re absolutely right that: key-placement validation would occur during schema construction rather than at write time. However, the current StaticAttributeTag API does not expose the fully resolved TableMetadata during modifyMetadata(). This is a limitation of the current design. To address the performance concern, the implementation validates once per TableMetadata instance (on first use) and caches the computed set of allowed key attributes. See the comment added below for for additional context: #6373 (comment). |
Hello @shetsa-amzn, Thank you for the feedback! I have updated the PR description in order to reflect this with: This PR is related to issue #5497, but it does not modify or fix the behavior of Changing the behavior of Instead, this PR introduces This functionality was present in V1 under |
|
Hello @shetsa-amzn @amzn-erdemkemer, I have added functional tests covering composite primary and secondary index keys scenarios, The tests were added in AutoGeneratedKeyCompositeGsiTest.java and validate correct UUID generation and update behavior for composite keys across root and flattened attributes. Bean Structure:BeanWithMixedCompositeGsi class includes: 1. Primary Key:
2. Composite GSI Keys (Root):
3. Flattened Composite Keys:
Test Scenarios:1. putItem_whenKeysNotPopulated_generatesNewUuids: 2. putItem_whenKeysAlreadyPopulated_preservesExistingUuids: 3. updateItem_respectsUpdateBehavior:
4. batchWrite_whenKeysNotAlreadyPopulated_generatesNewUuids: 5. batchWrite_whenKeysAlreadyPopulated_preservesExistingUuids: 6. transactWrite_whenKeysNotAlreadyPopulated_generatesNewUuids: 7. transactWrite_whenKeysAlreadyPopulated_preservesExistingUuids: |
...azon/awssdk/enhanced/dynamodb/functionaltests/extensions/AutoGeneratedUuidExtensionTest.java
Outdated
Show resolved
Hide resolved
…erated-key-annotation' into feature/define-dynamo-db-autogenerated-key-annotation
LeeroyHannigan
left a comment
There was a problem hiding this comment.
I do not see any tests for multi-attribute keys for secondary indexes? Have you tested this works, can you add a functional test case.
Hello @LeeroyHannigan, Functional coverage for multi-attribute (composite) secondary index keys are present in AutoGeneratedKeyCompositeGsiTest.java. Bean Structure Used in the Tests: The test bean (BeanWithMixedCompositeGsi) is structured to cover:
1) Composite Primary Key @DynamoDbPartitionKey
@DynamoDbAutoGeneratedKey
public String getId()
@DynamoDbSortKey
@DynamoDbAutoGeneratedKey
public String getSort()Behavior:
2) Composite GSI Keys (Root Attributes): Example for partition key parts: @DynamoDbAutoGeneratedKey
@DynamoDbUpdateBehavior(UpdateBehavior.WRITE_ALWAYS)
@DynamoDbSecondaryPartitionKey(indexNames = {...}, order = FIRST)
public String getRootPartitionKey1()
@DynamoDbAutoGeneratedKey
@DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
@DynamoDbSecondaryPartitionKey(indexNames = {...}, order = SECOND)
public String getRootPartitionKey2()Example for sort key parts: @DynamoDbAutoGeneratedKey
@DynamoDbUpdateBehavior(UpdateBehavior.WRITE_ALWAYS)
@DynamoDbSecondarySortKey(indexNames = {...}, order = FIRST)
public String getRootSecondaryKey1()
@DynamoDbAutoGeneratedKey
@DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
@DynamoDbSecondarySortKey(indexNames = {...}, order = SECOND)
public String getRootSecondaryKey2()Behavior:
3) Composite GSI Keys (Flattened Attributes): The bean also extends the composite keys using a flattened object: @DynamoDbFlatten
public FlattenedKeys getFlattenedKeys()Inside: @DynamoDbAutoGeneratedKey
@DynamoDbUpdateBehavior(UpdateBehavior.WRITE_ALWAYS)
@DynamoDbSecondaryPartitionKey(indexNames = {...}, order = THIRD)
public String getFlattenedPartitionKey1()This means the composite GSI key is built across both root and flattened attributes. What the Tests Cover: The following operations are tested:
Verified behavior:
These tests confirm that multi-attribute secondary index keys (including This comment can be checked for more context regarding the tests scenarios added in AutoGeneratedKeyCompositeGsiTest.java. |
|
|
Hi @anasatirbasa, Thanks for your patience on this. I know it's been a long review cycle and I appreciate the work you've put in. I want to revisit something I should have caught earlier in the review process, and I apologize for not raising it sooner. While looking at this more closely with the team, I went back to the v1 SDK to understand how it originally handled this. In v1, the design was:
So This raises the question, what if instead of introducing a new annotation and extension, we added a @DynamoDbAutoGeneratedUuid(strategy = DynamoDbAutoGenerateStrategy.CREATE)With the default set to ALWAYS, this would be backwards compatible. Existing code using the annotation without parameters would behave exactly as it does today. Customers who want the "only generate when null" behavior would just set Here are the considerations that made me think we should take a step back and reconsider the existing design in favor of the proposed alternative:
I realize this is a significant pivot from the current approach, and I'm sorry for not connecting these dots earlier. Thanks, |



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
@DynamoDbAutoGeneratedUuidon the same attribute. If both annotations are applied to the same field, anIllegalArgumentExceptionwill 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:
@DynamoDbUpdateBehaviorhas 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 anyUpdateBehaviorsetting.Secondary Index Keys: For GSI/LSI keys,
@DynamoDbUpdateBehaviorcan be used to control generation: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 PR is related to issue #5497, but it does not modify or fix the behavior of
@DynamoDbAutoGeneratedUuid.As discussed in the above ticket,
AutoGeneratedUuidExtensionintentionally regenerates a UUID value on every write operation, including updates. When used on a partition key together with@DynamoDbVersionAttribute, this can lead to aConditionalCheckFailedExceptionbecause the primary key value changes between writes and the version condition check is evaluated against a different item.Changing the behavior of
@DynamoDbAutoGeneratedUuidwould be a breaking change for existing applications that rely on regeneration semantics.Instead, this PR introduces
@DynamoDbAutoGeneratedKeyas a safe alternative that only generates a value when the attribute is null or empty. This mirrors the legacy V1@DynamoDBAutoGeneratedKeybehavior and makes it suitable for use as a stable primary key in combination with@DynamoDbVersionAttribute.This functionality was present in V1 under
@DynamoDBAutoGeneratedKey. Many users requested its reintroduction in V2.Modifications
@DynamoDbAutoGeneratedKeyin thesoftware.amazon.awssdk.enhanced.dynamodb.extensions.annotationspackage.AutoGeneratedKeyExtension, which ensures attributes annotated with@DynamoDbAutoGeneratedKeyare populated with a UUID when absent. This usesUUID.randomUUID()under the hood.AutoGeneratedKeyTagas the annotation tag integration point.@DynamoDbAutoGeneratedKeyand@DynamoDbAutoGeneratedUuidto prevent unpredictable behavior.@DynamoDbUpdateBehavioronly affects secondary index keys, not primary keys.@DynamoDbUpdateBehaviorto support both stable one-time keys - works for secondary index keys only (WRITE_IF_NOT_EXISTS) and regenerating keys (WRITE_ALWAYS).Stringis supported).AutoGeneratedKeyExtensionTest) and functional integration tests (AutoGeneratedKeyRecordTest) to verify correct behavior with real DynamoDB operations.AutoGeneratedKeyRecordTestandAutoGeneratedUuidRecordTestto verify exception throwing when conflicting annotations are used.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 on modified classes:
Test Coverage Checklist
Types of changes
Checklist
mvn installsucceedsscripts/new-changescript and following the instructions. Commit the new file created by the script in.changes/next-releasewith your changes.License