Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7cec4a2
Added support for DynamoDbAutoGeneratedKey annotation
anasatirbasa Nov 6, 2025
8b80b18
Merge branch 'master' into feature/define-dynamo-db-autogenerated-key…
anasatirbasa Nov 10, 2025
d615605
Added support for DynamoDbAutoGeneratedKey annotation
anasatirbasa Nov 14, 2025
da14ad5
Merge branch 'master' into feature/define-dynamo-db-autogenerated-key…
anasatirbasa Nov 16, 2025
031ef44
Added support for DynamoDbAutoGeneratedKey annotation
anasatirbasa Nov 16, 2025
a47b1d7
Merge branch 'master' into feature/define-dynamo-db-autogenerated-key…
anasatirbasa Dec 14, 2025
0a29161
Merge branch 'master' into feature/define-dynamo-db-autogenerated-key…
anasatirbasa Jan 11, 2026
1bf7a3a
Merge branch 'aws:master' into feature/define-dynamo-db-autogenerated…
anasatirbasa Jan 13, 2026
8c4826d
Increased unit and integration test coverage to 100%
anasatirbasa Jan 15, 2026
8a3048f
Merge branch 'master' into feature/define-dynamo-db-autogenerated-key…
anasatirbasa Feb 11, 2026
79ba90f
Increased code coverage
anasatirbasa Feb 12, 2026
459fae8
Merge branch 'master' into feature/define-dynamo-db-autogenerated-key…
anasatirbasa Feb 12, 2026
8ae8405
Tests refactoring
anasatirbasa Feb 12, 2026
08012f9
Tests refactoring
anasatirbasa Feb 12, 2026
67cd161
Addressed PR feedback and refactored tests
anasatirbasa Feb 13, 2026
a4bfb1e
Addressed PR feedback
anasatirbasa Feb 13, 2026
fd877c7
Merge branch 'master' into feature/define-dynamo-db-autogenerated-key…
anasatirbasa Feb 16, 2026
bbd03ac
Added tests with composite gsi
anasatirbasa Feb 16, 2026
e654ef5
Added tests with composite gsi
anasatirbasa Feb 16, 2026
8c3d7d1
Added tests with composite gsi
anasatirbasa Feb 16, 2026
6d66b77
Added tests with composite gsi
anasatirbasa Feb 16, 2026
5bad65a
Merge branch 'master' into feature/define-dynamo-db-autogenerated-key…
anasatirbasa Feb 17, 2026
b935acf
Fixed assertions on generated uuids
anasatirbasa Feb 18, 2026
45c4000
Merge remote-tracking branch 'origin/feature/define-dynamo-db-autogen…
anasatirbasa Feb 18, 2026
407703e
Merge branch 'master' into feature/define-dynamo-db-autogenerated-key…
anasatirbasa Feb 18, 2026
9a44ed2
Merge branch 'master' into feature/define-dynamo-db-autogenerated-key…
anasatirbasa Feb 20, 2026
dea26e0
Merge branch 'master' into feature/define-dynamo-db-autogenerated-key…
anasatirbasa Apr 6, 2026
defb044
Merge remote-tracking branch 'origin/feature/define-dynamo-db-autogen…
anasatirbasa Apr 6, 2026
8b057b5
Merge branch 'master' into feature/define-dynamo-db-autogenerated-key…
anasatirbasa Apr 28, 2026
68ca368
Merge branch 'master' into feature/define-dynamo-db-autogenerated-key…
anasatirbasa May 4, 2026
ae9e8ad
Add support for strategy-based UUID auto-generation (@DynamoDbAutoGen…
anasatirbasa May 6, 2026
b72323f
Merge branch 'master' into feature/define-dynamo-db-autogenerated-key…
anasatirbasa Aug 18, 2026
0cdc2c2
Addressed PR comments
anasatirbasa Aug 19, 2026
9fe582f
Javadoc update for DynamoDbAutoGenerateStrategy and DynamoDbAutoGener…
andreas-grafenberger Aug 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "Amazon DynamoDB Enhanced Client",
"contributor": "",
"description": "Added strategy-based UUID generation to DynamoDbAutoGeneratedUuid with ALWAYS (default) and CREATE modes."
}
13 changes: 13 additions & 0 deletions services-custom/dynamodb-enhanced/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@
</archive>
</configuration>
</plugin>
<plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
<configuration>
<parameter>
<excludes>
<exclude>
software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid#strategy()
</exclude>
</excludes>
</parameter>
</configuration>
</plugin>
</plugins>
<pluginManagement>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

package software.amazon.awssdk.enhanced.dynamodb.extensions;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
Expand All @@ -27,17 +26,17 @@
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClientExtension;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbExtensionContext;
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGenerateStrategy;
import software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTag;
import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbUpdateBehavior;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.utils.Validate;


/**
* This extension facilitates the automatic generation of a unique UUID (Universally Unique Identifier) for a specified attribute
* every time a new record is written to the database. The generated UUID is obtained using the
* {@link java.util.UUID#randomUUID()} method.
* This extension facilitates the automatic generation of a UUID value for tagged string attributes.
* UUID values are generated using {@link java.util.UUID#randomUUID()}.
* <p>
* This extension is not loaded by default when you instantiate a
* {@link software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient}. Therefore, you need to specify it in a custom
Expand All @@ -56,30 +55,41 @@
*}
* </p>
* <p>
* To utilize the auto-generated UUID feature, first, create a field in your model that will store the UUID for the attribute.
* This class field must be of type {@link java.lang.String}, and you need to tag it as the autoGeneratedUuidAttribute. If you are
* To use auto-generated UUID values, create a field in your model that stores the UUID attribute.
* This field must be of type {@link java.lang.String}, and you need to tag it as auto-generated UUID. If you are
* using the {@link software.amazon.awssdk.enhanced.dynamodb.mapper.BeanTableSchema}, then you should use the
* {@link software.amazon.awssdk.enhanced.dynamodb.extensions.annotations.DynamoDbAutoGeneratedUuid} annotation. If you are using
* {@link DynamoDbAutoGeneratedUuid} annotation. If you are using
* the {@link software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema}, then you should use the
* {@link
* software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension.AttributeTags#autoGeneratedUuidAttribute()}
* static attribute tag.
* </p>
* <p>
* 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}
*
* Generation behavior is controlled by {@link DynamoDbAutoGenerateStrategy}:
* <ul>
* <li>{@code ALWAYS}: generate a new UUID on every write handled by this extension.</li>
* <li>{@code CREATE}: generate a UUID only when the current value is missing from the write item map.
* Missing means the value is absent or DynamoDB {@code NULL}. An empty string is treated as present.</li>
* </ul>
* <p>
* {@code CREATE} inspects the write item map after mapping, not the value stored in DynamoDB.
* With {@code updateItem} and {@code ignoreNulls(true)}, a null CREATE field is omitted from the map, so a new UUID
* is generated and silently overwrites any existing stored value.
* </p>
* <p>
* {@link software.amazon.awssdk.enhanced.dynamodb.mapper.UpdateBehavior} is a separate concern used while building update
* expressions for {@code updateItem}. It does not change this extension's strategy decision.
* </p>
*/
@SdkPublicApi
@ThreadSafe
public final class AutoGeneratedUuidExtension implements DynamoDbEnhancedClientExtension {
private static final String CUSTOM_METADATA_KEY =
"software.amazon.awssdk.enhanced.dynamodb.extensions.AutoGeneratedUuidExtension:AutoGeneratedUuidAttribute";
private static final AutoGeneratedUuidAttribute AUTO_GENERATED_UUID_ATTRIBUTE = new AutoGeneratedUuidAttribute();
private static final AutoGeneratedUuidAttribute AUTO_GENERATED_UUID_ALWAYS_ATTRIBUTE =
new AutoGeneratedUuidAttribute(DynamoDbAutoGenerateStrategy.ALWAYS);
private static final AutoGeneratedUuidAttribute AUTO_GENERATED_UUID_CREATE_ATTRIBUTE =
new AutoGeneratedUuidAttribute(DynamoDbAutoGenerateStrategy.CREATE);

private AutoGeneratedUuidExtension() {
}
Expand All @@ -101,42 +111,69 @@ public static AutoGeneratedUuidExtension create() {
public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {


Collection<String> customMetadataObject = context.tableMetadata()
.customMetadataObject(CUSTOM_METADATA_KEY, Collection.class)
.orElse(null);
Map<String, DynamoDbAutoGenerateStrategy> customMetadataObject =
context.tableMetadata()
.customMetadataObject(CUSTOM_METADATA_KEY, Map.class)
.orElse(null);

if (customMetadataObject == null) {
return WriteModification.builder().build();
}

Map<String, AttributeValue> itemToTransform = new HashMap<>(context.items());
customMetadataObject.forEach(key -> insertUuidInItemToTransform(itemToTransform, key));
customMetadataObject.forEach((key, strategy) -> insertUuidInItemToTransform(itemToTransform, key, strategy));
return WriteModification.builder()
.transformedItem(Collections.unmodifiableMap(itemToTransform))
.build();
}

private void insertUuidInItemToTransform(Map<String, AttributeValue> itemToTransform,
String key) {
String key,
DynamoDbAutoGenerateStrategy strategy) {
if (strategy == DynamoDbAutoGenerateStrategy.CREATE && !isMissingValue(itemToTransform.get(key))) {
return;
}
itemToTransform.put(key, AttributeValue.builder().s(UUID.randomUUID().toString()).build());
}

private boolean isMissingValue(AttributeValue currentValue) {
return currentValue == null || Boolean.TRUE.equals(currentValue.nul());
}

public static final class AttributeTags {

private AttributeTags() {
}

/**
* Tags which indicate that the given attribute is supported wih Auto Generated UUID Record Extension.
* Tags which indicate that the given attribute is supported with Auto Generated UUID Record Extension.
*
* @return Tag name for AutoGenerated UUID Records
*/
public static StaticAttributeTag autoGeneratedUuidAttribute() {
return AUTO_GENERATED_UUID_ATTRIBUTE;
return AUTO_GENERATED_UUID_ALWAYS_ATTRIBUTE;
}

/**
* Tags which indicate that the given attribute is supported with Auto Generated UUID Record Extension and
* applies the provided strategy.
*
* @return Tag name for AutoGenerated UUID Records
*/
public static StaticAttributeTag autoGeneratedUuidAttribute(DynamoDbAutoGenerateStrategy strategy) {
Validate.notNull(strategy, "strategy is null");
return strategy == DynamoDbAutoGenerateStrategy.CREATE
? AUTO_GENERATED_UUID_CREATE_ATTRIBUTE
: AUTO_GENERATED_UUID_ALWAYS_ATTRIBUTE;
}
}

private static class AutoGeneratedUuidAttribute implements StaticAttributeTag {
private final DynamoDbAutoGenerateStrategy strategy;

private AutoGeneratedUuidAttribute(DynamoDbAutoGenerateStrategy strategy) {
this.strategy = strategy;
}

@Override
public <R> void validateType(String attributeName, EnhancedType<R> type,
Expand All @@ -156,7 +193,8 @@ public <R> void validateType(String attributeName, EnhancedType<R> type,
@Override
public Consumer<StaticTableMetadata.Builder> modifyMetadata(String attributeName,
AttributeValueType attributeValueType) {
return metadata -> metadata.addCustomMetadataObject(CUSTOM_METADATA_KEY, Collections.singleton(attributeName))
return metadata -> metadata.addCustomMetadataObject(
CUSTOM_METADATA_KEY, Collections.singletonMap(attributeName, strategy))
.markAttributeAsKey(attributeName, attributeValueType);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.enhanced.dynamodb.extensions.annotations;

import software.amazon.awssdk.annotations.SdkPublicApi;

/**
* Strategy used to decide when a new value is generated for an annotated attribute.
*/
@SdkPublicApi
public enum DynamoDbAutoGenerateStrategy {
/**
* Generate a new value on every write operation.
*/
ALWAYS,

/**
* Generate a value only when the current value is missing.
*/
CREATE
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,32 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.UUID;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.enhanced.dynamodb.internal.extensions.AutoGeneratedUuidTag;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.BeanTableSchemaAttributeTag;

/**
* Denotes this attribute as recording the auto generated UUID string for the record. Every time a record with this
* attribute is written to the database it will update the attribute with a {@link UUID#randomUUID} string.
* Denotes this attribute as recording the auto generated UUID string for the record.
* <p>
* The {@link #strategy()} controls whether UUID is generated on every write or only when missing.
* The default is {@link DynamoDbAutoGenerateStrategy#ALWAYS} for backward compatibility with existing
* {@code @DynamoDbAutoGeneratedUuid} usage.
* Use {@link DynamoDbAutoGenerateStrategy#CREATE} when you want to generate only if the value is missing
* (absent from the write item map or DynamoDB {@code NULL}).
* <p>
* {@code CREATE} inspects the write item map after mapping, not the value stored in DynamoDB.
* With {@code updateItem} and {@code ignoreNulls(true)}, a null CREATE field is omitted from the map, so a new UUID
* is generated and silently overwrites any existing stored value.
*/
@SdkPublicApi
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@BeanTableSchemaAttributeTag(AutoGeneratedUuidTag.class)
public @interface DynamoDbAutoGeneratedUuid {
}
/**
* Defines when a new UUID should be generated.
* <p>
* Defaults to {@link DynamoDbAutoGenerateStrategy#ALWAYS} to preserve backward compatibility.
*/
DynamoDbAutoGenerateStrategy strategy() default DynamoDbAutoGenerateStrategy.ALWAYS;
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ private AutoGeneratedUuidTag() {
}

public static StaticAttributeTag attributeTagFor(DynamoDbAutoGeneratedUuid annotation) {
return AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute();
return AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute(annotation.strategy());
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.enhanced.dynamodb;

import java.util.UUID;

public final class UuidTestUtils {

private UuidTestUtils() {
}

public static boolean isValidUuid(String uuid) {
try {
UUID.fromString(uuid);
return true;
} catch (Exception e) {
return false;
}
}
}
Loading
Loading