itemAttributes, DynamoDBMapperConfig config) {
+ throw new UnsupportedOperationException("operation not supported in " + getClass());
+ }
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/AttributeTransformer.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/AttributeTransformer.java
new file mode 100644
index 000000000000..4246b2c39a8c
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/AttributeTransformer.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2013-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import java.util.Map;
+
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+
+/**
+ * A hook allowing a custom transform/untransform of the raw attribute
+ * values immediately before writing them into DynamoDB and immediately
+ * after reading them out of DynamoDB, but with extra context about
+ * the model class not available at the raw AmazonDynamoDB level.
+ *
+ * This interface contains both a {@code transform} method and a corresponding
+ * {@code untransform} method. These methods SHOULD be inverses, such that
+ * untransform(transform(value)) == value.
+ */
+public interface AttributeTransformer {
+ /**
+ * Parameters for the {@code transform} and {@code untransform} methods,
+ * so we don't have to break the interface in order to add additional
+ * parameters.
+ *
+ * Consuming code should NOT implement this interface.
+ */
+ interface Parameters {
+ /**
+ * Returns the raw attribute values to be transformed or untransformed.
+ * The returned map is not modifiable.
+ *
+ * @return the raw attribute values to transform or untransform
+ */
+ Map getAttributeValues();
+
+ /**
+ * Returns true if this transformation is being called as part of a
+ * partial update operation. If true, the attributes returned by
+ * {@link #getAttributeValues()} do not represent the entire new
+ * item, but only a snapshot of the attributes which are getting
+ * new values.
+ *
+ * Implementations which do not support transforming a partial
+ * view of an item (for example, because they need to calculate a
+ * signature based on all of the item's attributes that won't be valid
+ * if only a subset of the attributes are taken into consideration)
+ * should check this flag and throw an exception rather than than
+ * corrupting the data in DynamoDB.
+ *
+ * This method always returns {@code false} for instances passed to
+ * {@link AttributeTransformer#untransform(Parameters)}.
+ *
+ * @return true if this operation is a partial update, false otherwise
+ */
+ boolean isPartialUpdate();
+
+ /**
+ * @return the type of the model class we're transforming to or from
+ */
+ Class getModelClass();
+
+ /**
+ * @return the mapper config for this operation
+ */
+ DynamoDBMapperConfig getMapperConfig();
+
+ /**
+ * @return the name of the DynamoDB table the attributes were read
+ * from or will be written to
+ */
+ String getTableName();
+
+ /**
+ * @return the name of the hash key for the table
+ */
+ String getHashKeyName();
+
+ /**
+ * @return the name of the range key for the table, if it has one,
+ * otherwise {@code null}
+ */
+ String getRangeKeyName();
+ }
+
+ /**
+ * Transforms the input set of attribute values derived from the model
+ * object before writing them into DynamoDB.
+ *
+ * @param parameters transformation parameters
+ * @return the transformed attribute value map
+ */
+ Map transform(Parameters> parameters);
+
+ /**
+ * Untransform the input set of attribute values read from DynamoDB before
+ * creating a model object from them.
+ *
+ * @param parameters transformation parameters
+ * @return the untransformed attribute value map
+ */
+ Map untransform(Parameters> parameters);
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/BatchLoadContext.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/BatchLoadContext.java
new file mode 100644
index 000000000000..faabf55a9e92
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/BatchLoadContext.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2010-2025 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.dynamodb.datamodeling;
+
+import software.amazon.awssdk.services.dynamodb.model.BatchGetItemRequest;
+import software.amazon.awssdk.services.dynamodb.model.BatchGetItemResponse;
+
+
+/**
+ * Container object that has information about the batch load request made to DynamoDB.
+ *
+ * @author avinam
+ */
+public class BatchLoadContext {
+ /**
+ * The BatchGetItemResponse returned by the DynamoDB client.
+ */
+ private BatchGetItemResponse batchGetItemResult;
+ /**
+ * The BatchGetItemRequest.
+ */
+ private final BatchGetItemRequest batchGetItemRequest;
+ /**
+ * The number of times the request has been retried.
+ */
+ private int retriesAttempted;
+
+ /**
+ * Instantiates a new BatchLoadContext.
+ * @param batchGetItemRequest see {@link BatchGetItemRequest}.
+ * */
+ public BatchLoadContext(BatchGetItemRequest batchGetItemRequest) {
+ this.batchGetItemRequest = java.util.Objects.requireNonNull(batchGetItemRequest, "batchGetItemRequest");
+ this.batchGetItemResult = null;
+ this.retriesAttempted = 0;
+ }
+
+ /**
+ * @return the BatchGetItemResponse
+ */
+ public BatchGetItemResponse getBatchGetItemResponse() {
+ return batchGetItemResult;
+ }
+
+ /**
+ * @return the BatchGetItemResponse
+ */
+ public void setBatchGetItemResponse(BatchGetItemResponse batchGetItemResult) {
+ this.batchGetItemResult = batchGetItemResult;
+ }
+
+
+ /**
+ * @return the BatchGetItemRequest.
+ */
+ public BatchGetItemRequest getBatchGetItemRequest() {
+ return batchGetItemRequest;
+ }
+
+ /**
+ * Gets the retriesAttempted.
+ *
+ * @return the retriesAttempted
+ */
+ public int getRetriesAttempted() {
+ return retriesAttempted;
+ }
+
+ /**
+ * Sets retriesAttempted.
+ *
+ * @param retriesAttempted the number of retries attempted
+ */
+ public void setRetriesAttempted(int retriesAttempted) {
+ this.retriesAttempted = retriesAttempted;
+ }
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/ConversionSchema.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/ConversionSchema.java
new file mode 100644
index 000000000000..12a37e5daea3
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/ConversionSchema.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2014-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A strategy for mapping between Java types and DynamoDB types. Serves as a
+ * factory for {@code ItemConverter} instances that implement this mapping.
+ * Standard implementations are available in the {@link ConversionSchemas}
+ * class.
+ */
+public interface ConversionSchema {
+
+ /**
+ * Dependency injection for the {@code ItemConverter}s that this
+ * {@code ConversionSchema} generates.
+ */
+ static class Dependencies {
+
+ private final Map, Object> values;
+
+ public Dependencies() {
+ values = new HashMap, Object>();
+ }
+
+ @SuppressWarnings("unchecked")
+ public T get(Class clazz) {
+ return (T) values.get(clazz);
+ }
+
+ public Dependencies with(Class clazz, T value) {
+ values.put(clazz, value);
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return values.toString();
+ }
+ }
+
+ /**
+ * Creates an {@code ItemConverter}, injecting dependencies from the
+ * {@code DynamoDBMapper} that needs it.
+ *
+ * @param dependencies the dependencies to inject
+ * @return a new ItemConverter
+ */
+ ItemConverter getConverter(Dependencies dependencies);
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/ConversionSchemas.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/ConversionSchemas.java
new file mode 100644
index 000000000000..778464aeb984
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/ConversionSchemas.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2016-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import software.amazon.awssdk.dynamodb.datamodeling.DynamoDBMapperFieldModel.DynamoDBAttributeType;
+import software.amazon.awssdk.dynamodb.datamodeling.StandardModelFactories.Rule;
+import software.amazon.awssdk.dynamodb.datamodeling.StandardModelFactories.RuleFactory;
+
+/**
+ * Pre-defined strategies for mapping between Java types and DynamoDB types.
+ */
+public final class ConversionSchemas {
+
+ /**
+ * The V1 schema mapping, which retains strict backwards compatibility with
+ * the original DynamoDB data model. This is compatible with the
+ * {@link DynamoDBTyped} annotation.
+ */
+ public static final ConversionSchema V1 = new NamedConversionSchema("V1ConversionSchema");
+
+ /**
+ * A V2 compatible conversion schema which is the default. Supports
+ * both V1 and V2 style annotations.
+ */
+ public static final ConversionSchema V2_COMPATIBLE = new NamedConversionSchema("V2CompatibleConversionSchema");
+
+ /**
+ * The V2 schema mapping, which removes support for some legacy types.
+ */
+ public static final ConversionSchema V2 = new NamedConversionSchema("V2ConversionSchema");
+
+ static final ConversionSchema DEFAULT = V2_COMPATIBLE;
+
+ private static final class NamedConversionSchema implements ConversionSchema {
+ private final String name;
+ private NamedConversionSchema(String name) {
+ this.name = name;
+ }
+ @Override
+ public ItemConverter getConverter(Dependencies depends) {
+ throw new UnsupportedOperationException(
+ "Legacy ItemConverter not supported; use StandardModelFactories rules");
+ }
+ @Override
+ public String toString() {
+ return name;
+ }
+ }
+
+ /**
+ * Rule factory that wraps the standard type converter rules.
+ * For built-in schemas (V1, V2_COMPATIBLE, V2), delegates directly to the
+ * wrapped rules. Custom schemas are not supported in this port.
+ */
+ static class ItemConverterRuleFactory implements RuleFactory {
+ private final RuleFactory typeConverters;
+
+ ItemConverterRuleFactory(DynamoDBMapperConfig config, RuleFactory typeConverters) {
+ this.typeConverters = typeConverters;
+ }
+
+ @Override
+ public Rule getRule(ConvertibleType type) {
+ return typeConverters.getRule(type);
+ }
+ }
+
+ ConversionSchemas() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/ConvertibleType.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/ConvertibleType.java
new file mode 100644
index 000000000000..8a5cf2bf4d7d
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/ConvertibleType.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2016-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import software.amazon.awssdk.dynamodb.datamodeling.DynamoDBMapperFieldModel.DynamoDBAttributeType;
+import software.amazon.awssdk.dynamodb.datamodeling.StandardAnnotationMaps.TypedMap;
+import software.amazon.awssdk.dynamodb.datamodeling.StandardTypeConverters.Scalar;
+import software.amazon.awssdk.dynamodb.datamodeling.StandardTypeConverters.Vector;
+import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+/**
+ * Generic type helper.
+ */
+final class ConvertibleType {
+
+ private final DynamoDBTypeConverter,T> typeConverter;
+ private final DynamoDBAttributeType attributeType;
+ private final ConvertibleType[] params;
+ private final Class targetType;
+
+ @Deprecated
+ private final Method getter, setter;
+
+ /**
+ * Constructs a new parameter type.
+ */
+ @SuppressWarnings("unchecked")
+ private ConvertibleType(Type genericType, TypedMap annotations, Method getter) {
+ this.typeConverter = annotations.typeConverter();
+ this.attributeType = annotations.attributeType();
+
+ if (typeConverter != null) {
+ final ConvertibleType target = ConvertibleType.of(typeConverter);
+ this.targetType = target.targetType;
+ this.params = target.params;
+ } else if (genericType instanceof ParameterizedType) {
+ final Type[] paramTypes = ((ParameterizedType)genericType).getActualTypeArguments();
+ this.targetType = annotations.targetType();
+ this.params = new ConvertibleType[paramTypes.length];
+ for (int i = 0; i < paramTypes.length; i++) {
+ this.params[i] = ConvertibleType.of(paramTypes[i]);
+ }
+ } else {
+ this.targetType = annotations.targetType();
+ this.params = new ConvertibleType[0];
+ }
+
+ this.setter = getter == null ? null : StandardBeanProperties.MethodReflect.setterOf(getter);
+ this.getter = getter;
+ }
+
+ /**
+ * Gets the target custom type-converter.
+ */
+ final DynamoDBTypeConverter typeConverter() {
+ return (DynamoDBTypeConverter)this.typeConverter;
+ }
+
+ /**
+ * Gets the overriding attribute type.
+ */
+ final DynamoDBAttributeType attributeType() {
+ return this.attributeType;
+ }
+
+ /**
+ * Gets the getter method.
+ */
+ @Deprecated
+ final Method getter() {
+ return this.getter;
+ }
+
+ /**
+ * Gets the setter method.
+ */
+ @Deprecated
+ final Method setter() {
+ return this.setter;
+ }
+
+ /**
+ * Gets the scalar parameter types.
+ */
+ final ConvertibleType param(final int index) {
+ return this.params.length > index ? (ConvertibleType)this.params[index] : null;
+ }
+
+ /**
+ * Returns true if the types match.
+ */
+ final boolean is(ScalarAttributeType scalarAttributeType, Vector vector) {
+ return param(0) != null && param(0).is(scalarAttributeType) && is(vector);
+ }
+
+ /**
+ * Returns true if the types match.
+ */
+ final boolean is(ScalarAttributeType scalarAttributeType) {
+ return Scalar.of(targetType()).is(scalarAttributeType);
+ }
+
+ /**
+ * Returns true if the types match.
+ */
+ final boolean is(Scalar scalar) {
+ return scalar.is(targetType());
+ }
+
+ /**
+ * Returns true if the types match.
+ */
+ final boolean is(Vector vector) {
+ return vector.is(targetType());
+ }
+
+ /**
+ * Returns true if the types match.
+ */
+ final boolean is(Class> type) {
+ return type.isAssignableFrom(targetType());
+ }
+
+ /**
+ * Gets the raw scalar type.
+ */
+ final Class targetType() {
+ return this.targetType;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder(targetType().getSimpleName());
+ if (this.params.length > 0) {
+ builder.append("<");
+ for (int i = 0; i < this.params.length; i++) {
+ builder.append(i == 0 ? "" : ",").append(this.params[i]);
+ }
+ builder.append(">");
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Returns the conversion type for the method and annotations.
+ */
+ static ConvertibleType of(Method getter, TypedMap annotations) {
+ return new ConvertibleType(getter.getGenericReturnType(), annotations, getter);
+ }
+
+ /**
+ * Returns the conversion type for the converter.
+ */
+ private static ConvertibleType of(final DynamoDBTypeConverter,T> converter) {
+ final Class> clazz = converter.getClass();
+ if (!clazz.isInterface()) {
+ for (Class> c = clazz; Object.class != c; c = c.getSuperclass()) {
+ for (final Type genericType : c.getGenericInterfaces()) {
+ final ConvertibleType type = ConvertibleType.of(genericType);
+ if (type.is(DynamoDBTypeConverter.class)) {
+ if (type.params.length == 2 && type.param(0).targetType() != Object.class) {
+ return type.param(0);
+ }
+ }
+ }
+ }
+ final ConvertibleType type = ConvertibleType.of(clazz.getGenericSuperclass());
+ if (type.is(DynamoDBTypeConverter.class)) {
+ if (type.params.length > 0 && type.param(0).targetType() != Object.class) {
+ return type.param(0);
+ }
+ }
+ }
+ throw new DynamoDBMappingException("could not resolve type of " + clazz);
+ }
+
+ /**
+ * Returns the conversion type for the generic type.
+ */
+ private static ConvertibleType of(Type genericType) {
+ final Class targetType;
+ if (genericType instanceof Class) {
+ targetType = (Class)genericType;
+ } else if (genericType instanceof ParameterizedType) {
+ targetType = (Class)((ParameterizedType)genericType).getRawType();
+ } else if (genericType.toString().equals("byte[]")) {
+ targetType = (Class)byte[].class;
+ } else {
+ targetType = (Class)Object.class;
+ }
+ final TypedMap annotations = StandardAnnotationMaps.of(targetType);
+ return new ConvertibleType(genericType, annotations, null);
+ }
+
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDB.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDB.java
new file mode 100644
index 000000000000..6dbee2d7d887
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDB.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011-2025 Amazon Technologies, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to mark other annotations as being part of DyanmoDB.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+public @interface DynamoDB {
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBAttribute.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBAttribute.java
new file mode 100644
index 000000000000..1fd111c93e7e
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBAttribute.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Interface for marking a class property as an attribute in a DynamoDB table.
+ * Applied to the getter method or the class field for a modeled property. If
+ * the annotation is applied directly to the class field, the corresponding
+ * getter and setter must be declared in the same class.
+ *
+ * This annotation is optional when the name of the DynamoDB attribute matches
+ * the name of the property declared in the class. When they differ, use this
+ * annotation with the attributeName() parameter to specify which DynamoDB
+ * attribute this property corresponds to. Furthermore, the
+ * {@link DynamoDBMapper} class assumes Java naming conventions, and will
+ * lower-case the first character of a getter method's property name to
+ * determine the name of the property. E.g., a method getValue() will map to the
+ * DynamoDB attribute "value". Similarly, a method isValid() maps to the
+ * DynamoDB attribute "valid".
+ *
+ * Getter methods not marked with this annotation are assumed to be modeled
+ * properties, unless marked with {@link DynamoDBIgnore}.
+ */
+@DynamoDB
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface DynamoDBAttribute {
+
+ /**
+ * Optional parameter when the name of the attribute as stored in DynamoDB
+ * should differ from the name used by the getter / setter.
+ */
+ String attributeName() default "";
+
+ /**
+ * Optional parameter when using {@link DynamoDBFlattened}; identifies
+ * the field/property name on the target class to map as the attribute.
+ * @see com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBFlattened
+ */
+ String mappedBy() default "";
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBAutoGenerateStrategy.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBAutoGenerateStrategy.java
new file mode 100644
index 000000000000..62a7cf3ff820
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBAutoGenerateStrategy.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010-2025 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.dynamodb.datamodeling;
+
+/**
+ * Enumeration of possible auto-generation strategies.
+ * @see DynamoDBAutoGeneratedTimestamp
+ */
+public enum DynamoDBAutoGenerateStrategy {
+
+ /**
+ * Instructs to always generate both on create and update.
+ */
+ ALWAYS,
+
+ /**
+ * Instructs to generate on create only.
+ */
+ CREATE;
+
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBAutoGenerated.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBAutoGenerated.java
new file mode 100644
index 000000000000..b0d6dc92e066
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBAutoGenerated.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2011-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to mark a property as using a custom auto-generator.
+ *
+ *
May be annotated on a user-defined annotation to pass additional
+ * properties to the {@link DynamoDBAutoGenerator}.
+ *
+ *
+ * @DynamoDBHashKey
+ * @CustomGeneratedKey(prefix="test-") //<- user-defined annotation
+ * public String getKey()
+ *
+ *
+ * Where,
+ *
+ * @DynamoDBAutoGenerated(generator=CustomGeneratedKey.Generator.class)
+ * @Retention(RetentionPolicy.RUNTIME)
+ * @Target({ElementType.METHOD})
+ * public @interface CustomGeneratedKey {
+ * String prefix() default "";
+ *
+ * public static class Generator implements DynamoDBAutoGenerator<String> {
+ * private final String prefix;
+ * public Generator(final Class<String> targetType, final CustomGeneratedKey annotation) {
+ * this.prefix = annotation.prefix();
+ * }
+ * public Generator() { //<- required if annotating directly
+ * this.prefix = "";
+ * }
+ * @Override
+ * public DynamoDBAutoGenerateStrategy getGenerateStrategy() {
+ * return DynamoDBAutoGenerateStrategy.CREATE;
+ * }
+ * @Override
+ * public final String generate(final String currentValue) {
+ * return prefix + UUID.randomUUID.toString();
+ * }
+ * }
+ * }
+ *
+ *
+ * Alternately, the property/field may be annotated directly (which requires
+ * the generator to provide a default constructor),
+ *
+ * @DynamoDBAutoGenerated(generator=CustomGeneratedKey.Generator.class)
+ * public String getKey()
+ *
+ *
+ * May be used as a meta-annotation.
+ */
+@DynamoDB
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
+public @interface DynamoDBAutoGenerated {
+
+ /**
+ * The auto-generator class for this property.
+ */
+ @SuppressWarnings("rawtypes")
+ Class extends DynamoDBAutoGenerator> generator();
+
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBAutoGenerator.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBAutoGenerator.java
new file mode 100644
index 000000000000..c0b496a7b89d
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBAutoGenerator.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+
+/**
+ * Generator interface for auto-generating attribute values.
+ *
+ * Auto-generation may be controlled by {@link DynamoDBAutoGenerateStrategy},
+
+ *
{@link DynamoDBAutoGenerateStrategy#CREATE}, instructs to generate when
+ * creating the item. The mapper, determines an item is new, or overwriting,
+ * if it's current value is {@code null}. There is a limitiation when performing
+ * partial updates using either,
+ * {@link DynamoDBMapperConfig.SaveBehavior#UPDATE_SKIP_NULL_ATTRIBUTES}, or
+ * {@link DynamoDBMapperConfig.SaveBehavior#APPEND_SET}. A new value will only
+ * be generated if the mapper is also generating the key.
+ *
+ * {@link DynamoDBAutoGenerateStrategy#ALWAYS}, instructs to always generate
+ * a new value, applied on any save or batch write operation.
+ *
+ *
May be used in combination with {@link DynamoDBAutoGenerated}.
+ *
+ * @param The object's field/property value type.
+ *
+ * @see com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAutoGenerated
+ */
+public interface DynamoDBAutoGenerator {
+
+ /**
+ * Gets the auto-generate strategy.
+ */
+ public DynamoDBAutoGenerateStrategy getGenerateStrategy();
+
+ /**
+ * Generates a new value given the current value (or null) if applicable.
+ */
+ public T generate(T currentValue);
+
+ /**
+ * A generator which holds the {@link DynamoDBAutoGenerateStrategy}.
+ */
+ static abstract class AbstractGenerator implements DynamoDBAutoGenerator {
+ private final DynamoDBAutoGenerateStrategy strategy;
+
+ protected AbstractGenerator(DynamoDBAutoGenerateStrategy strategy) {
+ this.strategy = strategy;
+ }
+
+ @Override
+ public DynamoDBAutoGenerateStrategy getGenerateStrategy() {
+ return this.strategy;
+ }
+ }
+
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBDocument.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBDocument.java
new file mode 100644
index 000000000000..5cb52764c35b
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBDocument.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation that marks a class which can be serialized to a DynamoDB
+ * document or sub-document. Behaves exactly the same as {@link DynamoDBTable},
+ * but without requiring you to specify a {@code tableName}.
+ */
+@DynamoDB
+@DynamoDBTyped(DynamoDBMapperFieldModel.DynamoDBAttributeType.M)
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface DynamoDBDocument {
+
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBFlattened.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBFlattened.java
new file mode 100644
index 000000000000..80eaeb786e80
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBFlattened.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2016-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for flattening a complex type.
+ *
+ *
+ * @DynamoDBFlattened(attributes={
+ * @DynamoDBAttribute(mappedBy="start", attributeName="effectiveStartDate"),
+ * @DynamoDBAttribute(mappedBy="end", attributeName="effectiveEndDate")})
+ * public DateRange getEffectiveRange() { return effectiveRange; }
+ * public void setEffectiveRange(DateRange effectiveRange) { this.effectiveRange = effectiveRange; }
+ *
+ * @DynamoDBFlattened(attributes={
+ * @DynamoDBAttribute(mappedBy="start", attributeName="extensionStartDate"),
+ * @DynamoDBAttribute(mappedBy="end", attributeName="extensionEndDate")})
+ * public DateRange getExtensionRange() { return extensionRange; }
+ * public void setExtensionRange(DateRange extensionRange) { this.extensionRange = extensionRange; }
+ *
+ *
+ * Where,
+ *
+ * public class DateRange {
+ * private Date start;
+ * private Date end;
+ *
+ * public Date getStart() { return start; }
+ * public void setStart(Date start) { this.start = start; }
+ *
+ * public Date getEnd() { return end; }
+ * public void setEnd(Date end) { this.end = end; }
+ * }
+ *
+ *
+ * Attributes defined within the complex type may also be annotated.
+ *
+ * May be used as a meta-annotation.
+ */
+@DynamoDB
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
+public @interface DynamoDBFlattened {
+
+ /**
+ * Indicates the attributes that should be flattened.
+ */
+ DynamoDBAttribute[] attributes() default {};
+
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBHashKey.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBHashKey.java
new file mode 100644
index 000000000000..23a447ac0a25
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBHashKey.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for marking a property as the hash key for a modeled class.
+ * Applied to the getter method or the class field for a hash key property. If
+ * the annotation is applied directly to the class field, the corresponding
+ * getter and setter must be declared in the same class.
+ *
+ * This annotation is required.
+ */
+@DynamoDB
+@DynamoDBKeyed(software.amazon.awssdk.services.dynamodb.model.KeyType.HASH)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface DynamoDBHashKey {
+
+ /**
+ * Optional parameter when the name of the attribute as stored in DynamoDB
+ * should differ from the name used by the getter / setter.
+ */
+ String attributeName() default "";
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBIgnore.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBIgnore.java
new file mode 100644
index 000000000000..37271c9d722d
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBIgnore.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for marking a class property as non-modeled. Applied to the getter
+ * method or the class field for a non-modeled property. If the annotation is
+ * applied directly to the class field, the corresponding getter and setter must
+ * be declared in the same class.
+ *
+ * All getter methods not marked with this annotation are assumed to be modeled
+ * properties and included in any save() requests.
+ */
+@DynamoDB
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface DynamoDBIgnore {
+
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBIndexHashKey.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBIndexHashKey.java
new file mode 100644
index 000000000000..ab0453cc484c
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBIndexHashKey.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for marking a property in a class as the attribute to be used as
+ * the hash key for one or more global secondary indexes on a DynamoDB table.
+ * Applied to the getter method or the class field for the index hash key
+ * property. If the annotation is applied directly to the class field, the
+ * corresponding getter and setter must be declared in the same class.
+ *
+ * This annotation is required if this attribute will be used as index key for
+ * item queries.
+ */
+@DynamoDB
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface DynamoDBIndexHashKey {
+
+ /**
+ * Optional parameter when the name of the attribute as stored in DynamoDB
+ * should differ from the name used by the getter / setter.
+ */
+ String attributeName() default "";
+
+ /**
+ * Parameter for the name of the global secondary index.
+ *
+ * This is required if this attribute is the index key for only one global secondary
+ * index.
+ */
+ String globalSecondaryIndexName() default "";
+
+ /**
+ * Parameter for the names of the global secondary indexes.
+ * This is required if this attribute is the index key for multiple global secondary
+ * indexes.
+ */
+ String[] globalSecondaryIndexNames() default {};
+
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBIndexRangeKey.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBIndexRangeKey.java
new file mode 100644
index 000000000000..8bb2a86eca3c
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBIndexRangeKey.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2011-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for marking a property in a class as the attribute to be used as
+ * range key for one or more local secondary indexes on a DynamoDB table.
+ * Applied to the getter method or the class field for the indexed range key
+ * property. If the annotation is applied directly to the class field, the
+ * corresponding getter and setter must be declared in the same class.
+ *
+ * This annotation is required if this attribute will be used as index key for
+ * item queries.
+ */
+@DynamoDB
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface DynamoDBIndexRangeKey {
+
+ /**
+ * Optional parameter when the name of the attribute as stored in DynamoDB
+ * should differ from the name used by the getter / setter.
+ */
+ String attributeName() default "";
+
+ /**
+ * Parameter for the name of the local secondary index.
+ *
+ * This is required if this attribute is the index key for only one local secondary
+ * index.
+ */
+ String localSecondaryIndexName() default "";
+
+ /**
+ * Parameter for the names of the local secondary indexes.
+ *
+ * This is required if this attribute is the index key for multiple local secondary
+ * indexes.
+ */
+ String[] localSecondaryIndexNames() default {};
+
+ /**
+ * Parameter for the name of the global secondary index.
+ *
+ * This is required if this attribute is the index key for only one global secondary
+ * index.
+ */
+ String globalSecondaryIndexName() default "";
+
+ /**
+ * Parameter for the names of the global secondary indexes.
+ *
+ * This is required if this attribute is the index key for multiple global secondary
+ * indexes.
+ */
+ String[] globalSecondaryIndexNames() default {};
+
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBKeyed.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBKeyed.java
new file mode 100644
index 000000000000..23ae15ee5336
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBKeyed.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import software.amazon.awssdk.services.dynamodb.model.KeyType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for marking a property a key for a modeled class.
+ *
+ *
+ * @DynamoDBKeyed(KeyType.HASH)
+ * public UUID getKey()
+ *
+ *
+ * Alternately, the short-formed {@link DynamoDBHashKey}, and
+ * {@link DynamoDBRangeKey} may be used directly on the field/getter.
+ *
+ * May be used as a meta-annotation.
+ */
+@DynamoDB
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
+public @interface DynamoDBKeyed {
+
+ /**
+ * The primary key type; either {@code HASH} or {@code RANGE}.
+ */
+ KeyType value();
+
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBMapper.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBMapper.java
new file mode 100644
index 000000000000..1f395b312cc8
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBMapper.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2010-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
+import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Object mapper for domain-object interaction with DynamoDB.
+ * Port of the v1 DynamoDBMapper to work with the v2 DynamoDbClient.
+ * Stripped to load() path for POC.
+ */
+public class DynamoDBMapper extends AbstractDynamoDBMapper {
+
+ private final DynamoDbClient db;
+ private final DynamoDBMapperModelFactory models;
+ private final AttributeTransformer transformer;
+
+ private static final Logger log = LoggerFactory.getLogger(DynamoDBMapper.class);
+
+ public DynamoDBMapper(final DynamoDbClient dynamoDB) {
+ this(dynamoDB, DynamoDBMapperConfig.DEFAULT, null);
+ }
+
+ public DynamoDBMapper(final DynamoDbClient dynamoDB, final DynamoDBMapperConfig config) {
+ this(dynamoDB, config, null);
+ }
+
+ public DynamoDBMapper(
+ final DynamoDbClient dynamoDB,
+ final DynamoDBMapperConfig config,
+ final AttributeTransformer transformer) {
+ super(config);
+ this.db = dynamoDB;
+ this.transformer = transformer;
+ this.models = StandardModelFactories.of();
+ }
+
+ @Override
+ public DynamoDBMapperTableModel getTableModel(Class clazz, DynamoDBMapperConfig config) {
+ return this.models.getTableFactory(config).getTable(clazz);
+ }
+
+ @Override
+ public T load(T keyObject, DynamoDBMapperConfig config) {
+ @SuppressWarnings("unchecked")
+ Class clazz = (Class) keyObject.getClass();
+
+ config = mergeConfig(config);
+ final DynamoDBMapperTableModel model = getTableModel(clazz, config);
+
+ String tableName = getTableName(clazz, keyObject, config);
+
+ Map key = model.convertKey(keyObject);
+
+ GetItemRequest rq = GetItemRequest.builder()
+ .tableName(tableName)
+ .key(key)
+ .consistentRead(config.getConsistentReads() == DynamoDBMapperConfig.ConsistentReads.CONSISTENT)
+ .build();
+
+ GetItemResponse item = db.getItem(rq);
+ Map itemAttributes = item.item();
+ if (itemAttributes == null || itemAttributes.isEmpty()) {
+ return null;
+ }
+
+ T object = privateMarshallIntoObject(
+ toParameters(itemAttributes, clazz, tableName, config));
+
+ return object;
+ }
+
+ @Override
+ public T load(Class clazz, Object hashKey, Object rangeKey, DynamoDBMapperConfig config) {
+ config = mergeConfig(config);
+ final DynamoDBMapperTableModel model = getTableModel(clazz, config);
+ T keyObject = model.createKey(hashKey, rangeKey);
+ return load(keyObject, config);
+ }
+
+ @Override
+ public T marshallIntoObject(Class clazz, Map itemAttributes, DynamoDBMapperConfig config) {
+ config = mergeConfig(config);
+ String tableName = getTableName(clazz, config);
+ return privateMarshallIntoObject(toParameters(itemAttributes, clazz, tableName, config));
+ }
+
+ /**
+ * The one true implementation of marshallIntoObject.
+ */
+ private T privateMarshallIntoObject(
+ AttributeTransformer.Parameters parameters) {
+
+ Class clazz = parameters.getModelClass();
+ Map values = untransformAttributes(parameters);
+
+ final DynamoDBMapperTableModel model = getTableModel(clazz, parameters.getMapperConfig());
+ return model.unconvert(values);
+ }
+
+ private AttributeTransformer.Parameters toParameters(
+ final Map attributeValues,
+ final Class modelClass,
+ final String tableName,
+ final DynamoDBMapperConfig mapperConfig) {
+
+ return new TransformerParameters(
+ getTableModel(modelClass, mapperConfig),
+ attributeValues,
+ false,
+ modelClass,
+ mapperConfig,
+ tableName);
+ }
+
+ private static class TransformerParameters
+ implements AttributeTransformer.Parameters {
+
+ private final DynamoDBMapperTableModel model;
+ private final Map attributeValues;
+ private final boolean partialUpdate;
+ private final Class modelClass;
+ private final DynamoDBMapperConfig mapperConfig;
+ private final String tableName;
+
+ public TransformerParameters(
+ final DynamoDBMapperTableModel model,
+ final Map attributeValues,
+ final boolean partialUpdate,
+ final Class modelClass,
+ final DynamoDBMapperConfig mapperConfig,
+ final String tableName) {
+
+ this.model = model;
+ this.attributeValues = Collections.unmodifiableMap(attributeValues);
+ this.partialUpdate = partialUpdate;
+ this.modelClass = modelClass;
+ this.mapperConfig = mapperConfig;
+ this.tableName = tableName;
+ }
+
+ @Override
+ public Map getAttributeValues() {
+ return attributeValues;
+ }
+
+ @Override
+ public boolean isPartialUpdate() {
+ return partialUpdate;
+ }
+
+ @Override
+ public Class getModelClass() {
+ return modelClass;
+ }
+
+ @Override
+ public DynamoDBMapperConfig getMapperConfig() {
+ return mapperConfig;
+ }
+
+ @Override
+ public String getTableName() {
+ return tableName;
+ }
+
+ @Override
+ public String getHashKeyName() {
+ return model.hashKey().name();
+ }
+
+ @Override
+ public String getRangeKeyName() {
+ return model.rangeKeyIfExists() == null ? null : model.rangeKey().name();
+ }
+ }
+
+ private Map untransformAttributes(
+ final AttributeTransformer.Parameters> parameters) {
+ if (transformer != null) {
+ return transformer.untransform(parameters);
+ } else {
+ return parameters.getAttributeValues();
+ }
+ }
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBMapperConfig.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBMapperConfig.java
new file mode 100644
index 000000000000..fd1e3ec29e08
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBMapperConfig.java
@@ -0,0 +1,1077 @@
+/*
+ * Copyright 2011-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import software.amazon.awssdk.services.dynamodb.model.KeysAndAttributes;
+import software.amazon.awssdk.services.dynamodb.model.WriteRequest;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * Immutable configuration object for service call behavior. An instance of this
+ * configuration is supplied to every {@link DynamoDBMapper} at construction; if
+ * not provided explicitly, {@link DynamoDBMapperConfig#DEFAULT} is used. New
+ * instances can be given to the mapper object on individual save, load, and
+ * delete operations to override the defaults. For example:
+ *
+ *
+ * DynamoDBMapper mapper = new DynamoDBMapper(dynamoDBClient);
+ * // Force this read to be consistent
+ * DomainClass obj = mapper.load(DomainClass.class, key, ConsistentReads.CONSISTENT.config());
+ * // Force this save operation to use putItem rather than updateItem
+ * mapper.save(obj, SaveBehavior.CLOBBER.config());
+ * // Save the object into a different table
+ * mapper.save(obj, new TableNameOverride("AnotherTable").config());
+ * // Delete the object even if the version field is out of date
+ * mapper.delete(obj, SaveBehavior.CLOBBER.config());
+ *
+ */
+public class DynamoDBMapperConfig {
+
+ /**
+ * Default configuration; these defaults are also applied by the mapper
+ * when only partial configurations are specified.
+ *
+ * @see SaveBehavior#UPDATE
+ * @see ConsistentReads#EVENTUAL
+ * @see PaginationLoadingStrategy#LAZY_LOADING
+ * @see DefaultTableNameResolver#INSTANCE
+ * @see DefaultBatchWriteRetryStrategy#INSTANCE
+ * @see DefaultBatchLoadRetryStrategy#INSTANCE
+ * @see DynamoDBTypeConverterFactory#standard
+ * @see ConversionSchemas#DEFAULT
+ */
+ public static final DynamoDBMapperConfig DEFAULT = builder()
+ .withSaveBehavior(SaveBehavior.UPDATE)
+ .withConsistentReads(ConsistentReads.EVENTUAL)
+ .withPaginationLoadingStrategy(PaginationLoadingStrategy.LAZY_LOADING)
+ .withTableNameResolver(DefaultTableNameResolver.INSTANCE)
+ .withBatchWriteRetryStrategy(DefaultBatchWriteRetryStrategy.INSTANCE)
+ .withBatchLoadRetryStrategy(DefaultBatchLoadRetryStrategy.INSTANCE)
+ .withTypeConverterFactory(DynamoDBTypeConverterFactory.standard())
+ .withConversionSchema(ConversionSchemas.DEFAULT)
+ .build();
+
+ /**
+ * Creates a new empty builder.
+ */
+ public static final Builder builder() {
+ return new Builder(false);
+ }
+
+ /**
+ * A fluent builder for DynamoDBMapperConfig objects.
+ */
+ public static class Builder {
+
+ private SaveBehavior saveBehavior;
+ private ConsistentReads consistentReads;
+ private TableNameOverride tableNameOverride;
+ private TableNameResolver tableNameResolver;
+ private ObjectTableNameResolver objectTableNameResolver;
+ private PaginationLoadingStrategy paginationLoadingStrategy;
+ private ConversionSchema conversionSchema;
+ private BatchWriteRetryStrategy batchWriteRetryStrategy;
+ private BatchLoadRetryStrategy batchLoadRetryStrategy;
+ private DynamoDBTypeConverterFactory typeConverterFactory;
+
+ /**
+ * Creates a new builder initialized with the {@link #DEFAULT} values.
+ */
+ public Builder() {
+ this(true);
+ }
+
+ /**
+ * Creates a new builder, optionally initialized with the defaults.
+ */
+ private Builder(final boolean defaults) {
+ if (defaults == true) {
+ saveBehavior = DEFAULT.getSaveBehavior();
+ consistentReads = DEFAULT.getConsistentReads();
+ paginationLoadingStrategy = DEFAULT.getPaginationLoadingStrategy();
+ conversionSchema = DEFAULT.getConversionSchema();
+ batchWriteRetryStrategy = DEFAULT.getBatchWriteRetryStrategy();
+ batchLoadRetryStrategy = DEFAULT.getBatchLoadRetryStrategy();
+ }
+ }
+
+ /**
+ * Merges any non-null configuration values for the specified overrides.
+ */
+ private final Builder merge(final DynamoDBMapperConfig o) {
+ if (o == null) return this;
+ if (o.saveBehavior != null) saveBehavior = o.saveBehavior;
+ if (o.consistentReads != null) consistentReads = o.consistentReads;
+ if (o.tableNameOverride != null) tableNameOverride = o.tableNameOverride;
+ if (o.tableNameResolver != null) tableNameResolver = o.tableNameResolver;
+ if (o.objectTableNameResolver != null) objectTableNameResolver = o.objectTableNameResolver;
+ if (o.paginationLoadingStrategy != null) paginationLoadingStrategy = o.paginationLoadingStrategy;
+ if (o.conversionSchema != null) conversionSchema = o.conversionSchema;
+ if (o.batchWriteRetryStrategy != null) batchWriteRetryStrategy = o.batchWriteRetryStrategy;
+ if (o.batchLoadRetryStrategy != null) batchLoadRetryStrategy = o.batchLoadRetryStrategy;
+ if (o.typeConverterFactory != null) typeConverterFactory = o.typeConverterFactory;
+ return this;
+ }
+
+ /**
+ * @return the currently-configured save behavior
+ */
+ public SaveBehavior getSaveBehavior() {
+ return saveBehavior;
+ }
+
+ /**
+ * @param value the new save behavior
+ */
+ public void setSaveBehavior(SaveBehavior value) {
+ saveBehavior = value;
+ }
+
+ /**
+ * @param value the new save behavior
+ * @return this builder
+ */
+ public Builder withSaveBehavior(SaveBehavior value) {
+ setSaveBehavior(value);
+ return this;
+ }
+
+
+ /**
+ * Returns the consistent read behavior. Currently
+ * this value is applied only in load and batch load operations of the
+ * DynamoDBMapper.
+ * @return the currently-configured consistent read behavior.
+ */
+ public ConsistentReads getConsistentReads() {
+ return consistentReads;
+ }
+
+ /**
+ * Sets the consistent read behavior. Currently
+ * this value is applied only in load and batch load operations of the
+ * DynamoDBMapper.
+ * @param value the new consistent read behavior.
+ */
+ public void setConsistentReads(ConsistentReads value) {
+ consistentReads = value;
+ }
+
+ /**
+ * Sets the consistent read behavior. Currently
+ * this value is applied only in load and batch load operations of the
+ * DynamoDBMapper.
+ * @param value the new consistent read behavior
+ * @return this builder.
+ *
+ */
+ public Builder withConsistentReads(ConsistentReads value) {
+ setConsistentReads(value);
+ return this;
+ }
+
+
+ /**
+ * @return the current table name override
+ */
+ public TableNameOverride getTableNameOverride() {
+ return tableNameOverride;
+ }
+
+ /**
+ * @param value the new table name override
+ */
+ public void setTableNameOverride(TableNameOverride value) {
+ tableNameOverride = value;
+ }
+
+ /**
+ * @param value the new table name override
+ * @return this builder
+ */
+ public Builder withTableNameOverride(TableNameOverride value) {
+ setTableNameOverride(value);
+ return this;
+ }
+
+
+ /**
+ * @return the current table name resolver
+ */
+ public TableNameResolver getTableNameResolver() {
+ return tableNameResolver;
+ }
+
+ /**
+ * @param value the new table name resolver
+ */
+ public void setTableNameResolver(TableNameResolver value) {
+ tableNameResolver = value;
+ }
+
+ /**
+ * @param value the new table name resolver
+ * @return this builder
+ */
+ public Builder withTableNameResolver(TableNameResolver value) {
+ setTableNameResolver(value);
+ return this;
+ }
+
+
+ /**
+ * @return the current object table name resolver
+ */
+ public ObjectTableNameResolver getObjectTableNameResolver() {
+ return objectTableNameResolver;
+ }
+
+ /**
+ * @param value the new object table name resolver
+ */
+ public void setObjectTableNameResolver(ObjectTableNameResolver value) {
+ objectTableNameResolver = value;
+ }
+
+ /**
+ * @param value the new object table name resolver
+ * @return this builder
+ */
+ public Builder withObjectTableNameResolver(ObjectTableNameResolver value) {
+ setObjectTableNameResolver(value);
+ return this;
+ }
+
+ /**
+ * @return the currently-configured pagination loading strategy
+ */
+ public PaginationLoadingStrategy getPaginationLoadingStrategy() {
+ return paginationLoadingStrategy;
+ }
+
+ /**
+ * @param value the new pagination loading strategy
+ */
+ public void setPaginationLoadingStrategy(
+ PaginationLoadingStrategy value) {
+
+ paginationLoadingStrategy = value;
+ }
+
+ /**
+ * @param value the new pagination loading strategy
+ * @return this builder
+ */
+ public Builder withPaginationLoadingStrategy(
+ PaginationLoadingStrategy value) {
+
+ setPaginationLoadingStrategy(value);
+ return this;
+ }
+
+
+ /**
+ * @return the current conversion schema
+ */
+ public ConversionSchema getConversionSchema() {
+ return conversionSchema;
+ }
+
+ /**
+ * @param value the new conversion schema
+ */
+ public void setConversionSchema(ConversionSchema value) {
+ conversionSchema = value;
+ }
+
+ /**
+ * @param value the new conversion schema
+ * @return this builder
+ */
+ public Builder withConversionSchema(ConversionSchema value) {
+ setConversionSchema(value);
+ return this;
+ }
+
+ /**
+ * @return the current BatchWriteRetryStrategy
+ */
+ public BatchWriteRetryStrategy getBatchWriteRetryStrategy() {
+ return batchWriteRetryStrategy;
+ }
+
+ /**
+ * @param value the new BatchWriteRetryStrategy
+ */
+ public void setBatchWriteRetryStrategy(
+ BatchWriteRetryStrategy value) {
+ this.batchWriteRetryStrategy = value;
+ }
+
+ /**
+ * @param value the new BatchWriteRetryStrategy
+ * @return this builder
+ */
+ public Builder withBatchWriteRetryStrategy(
+ BatchWriteRetryStrategy value) {
+ setBatchWriteRetryStrategy(value);
+ return this;
+ }
+
+ public BatchLoadRetryStrategy getBatchLoadRetryStrategy() {
+ return batchLoadRetryStrategy;
+ }
+
+ /**
+ * @param value the new BatchLoadRetryStrategy
+ */
+ public void setBatchLoadRetryStrategy(
+ BatchLoadRetryStrategy value) {
+ this.batchLoadRetryStrategy = value;
+ }
+
+ /**
+ * @param value the new BatchLoadRetryStrategy
+ * @return this builder
+ */
+ public Builder withBatchLoadRetryStrategy(
+ BatchLoadRetryStrategy value) {
+ //set the no retry strategy if the user overrides the default with null
+ if (value == null) {
+ value = NoRetryBatchLoadRetryStrategy.INSTANCE;
+ }
+ setBatchLoadRetryStrategy(value);
+ return this;
+ }
+
+ /**
+ * @return the current type-converter factory
+ */
+ public final DynamoDBTypeConverterFactory getTypeConverterFactory() {
+ return typeConverterFactory;
+ }
+
+ /**
+ * @param value the new type-converter factory
+ */
+ public final void setTypeConverterFactory(DynamoDBTypeConverterFactory value) {
+ this.typeConverterFactory = value;
+ }
+
+ /**
+ * The type-converter factory for scalar conversions.
+ * To override standard type-conversions,
+ *
+ * DynamoDBMapperConfig config = DynamoDBMapperConfig.builder()
+ * .withTypeConverterFactory(DynamoDBTypeConverterFactory.standard().override()
+ * .with(String.class, MyObject.class, new StringToMyObjectConverter())
+ * .build())
+ * .build();
+ *
+ * Then, on the property, specify the attribute binding,
+ *
+ * @DynamoDBTyped(DynamoDBAttributeType.S)
+ * public MyObject getMyObject()
+ *
+ * @param value the new type-converter factory
+ * @return this builder
+ */
+ public final Builder withTypeConverterFactory(DynamoDBTypeConverterFactory value) {
+ setTypeConverterFactory(value);
+ return this;
+ }
+
+ /**
+ * Builds a new {@code DynamoDBMapperConfig} object.
+ *
+ * @return the new, immutable config object
+ */
+ public DynamoDBMapperConfig build() {
+ return new DynamoDBMapperConfig(this);
+ }
+ }
+
+ /**
+ * Enumeration of behaviors for the save operation.
+ */
+ public static enum SaveBehavior {
+ /**
+ * UPDATE will not affect unmodeled attributes on a save operation and a
+ * null value for the modeled attribute will remove it from that item in
+ * DynamoDB.
+ *
+ * Because of the limitation of updateItem request, the implementation
+ * of UPDATE will send a putItem request when a key-only object is being
+ * saved, and it will send another updateItem request if the given
+ * key(s) already exists in the table.
+ *
+ * By default, the mapper uses UPDATE.
+ */
+ UPDATE,
+
+ /**
+ * UPDATE_SKIP_NULL_ATTRIBUTES is similar to UPDATE, except that it
+ * ignores any null value attribute(s) and will NOT remove them from
+ * that item in DynamoDB. It also guarantees to send only one single
+ * updateItem request, no matter the object is key-only or not.
+ */
+ UPDATE_SKIP_NULL_ATTRIBUTES,
+
+ /**
+ * CLOBBER will clear and replace all attributes on save, including unmodeled
+ * ones, and will also disregard versioned field constraints on conditional writes
+ * as well as overwriting auto-generated values regardless of existing values.
+ * If versioning is required, use {@link #PUT}.
+ */
+ CLOBBER,
+
+ /**
+ * PUT will clear and replace all attributes on save, including unmodeled
+ * ones, but fails if values do not match what is persisted on conditional writes
+ * and does not overwrite auto-generated values.
+ */
+ PUT,
+
+ /**
+ * APPEND_SET treats scalar attributes (String, Number, Binary) the same
+ * as UPDATE_SKIP_NULL_ATTRIBUTES does. However, for set attributes, it
+ * will append to the existing attribute value, instead of overriding
+ * it. Caller needs to make sure that the modeled attribute type matches
+ * the existing set type, otherwise it would result in a service
+ * exception.
+ */
+ APPEND_SET;
+
+ public final DynamoDBMapperConfig config() {
+ return builder().withSaveBehavior(this).build();
+ }
+ };
+
+ /**
+ * Enumeration of consistent read behavior.
+ *
+ * CONSISTENT uses consistent reads, EVENTUAL does not. Consistent reads
+ * have implications for performance and billing; see the service
+ * documentation for details.
+ *
+ * By default, the mapper uses eventual consistency.
+ */
+ public static enum ConsistentReads {
+ CONSISTENT,
+ EVENTUAL;
+
+ public final DynamoDBMapperConfig config() {
+ return builder().withConsistentReads(this).build();
+ }
+ };
+
+ /**
+ * Enumeration of pagination loading strategy.
+ */
+ public enum PaginationLoadingStrategy {
+ /**
+ * Paginated list is lazily loaded when possible, and all loaded results are kept in the memory. Data will
+ * only be fetched when accessed so this can be more performant than {@link #EAGER_LOADING} when not all
+ * data is used. Calls to methods such as {@link List#size()} will cause all results to be fetched from
+ * the service.
+ *
+ * By default, the mapper uses LAZY_LOADING.
+ */
+ LAZY_LOADING,
+
+ /**
+ * Only supports using iterator to read from the paginated list. All other list operations will return
+ * UnsupportedOperationException immediately. During the iteration, the list will clear all the
+ * previous results before loading the next page, so that the list will keep at most one page of the loaded results in
+ * memory. This also means the list could only be iterated once.
+ *
+ * Use this configuration to reduce the memory overhead when handling
+ * large DynamoDB items. This is the most performant option when you only need to iterate the results
+ * of a query.
+ */
+ ITERATION_ONLY,
+
+ /**
+ * Paginated list will eagerly load all the paginated results from DynamoDB as soon as the list is initialized. This may
+ * make several service calls when the list is created and is not recommended for large data sets. The benefit of using
+ * eager loading is that service call penalties are paid up front and you get predictable latencies when accessing the
+ * list afterwards since all contents are in memory.
+ */
+ EAGER_LOADING;
+
+ public final DynamoDBMapperConfig config() {
+ return builder().withPaginationLoadingStrategy(this).build();
+ }
+ }
+
+ /**
+ * Allows overriding the table name declared on a domain class by the
+ * {@link DynamoDBTable} annotation.
+ */
+ public static final class TableNameOverride {
+
+ private final String tableNameOverride;
+ private final String tableNamePrefix;
+
+ /**
+ * Returns a new {@link TableNameOverride} object that will prepend the
+ * given string to every table name.
+ */
+ public static TableNameOverride withTableNamePrefix(
+ String tableNamePrefix) {
+
+ return new TableNameOverride(null, tableNamePrefix);
+ }
+
+ /**
+ * Returns a new {@link TableNameOverride} object that will replace
+ * every table name in requests with the given string.
+ */
+ public static TableNameOverride withTableNameReplacement(
+ String tableNameReplacement) {
+
+ return new TableNameOverride(tableNameReplacement, null);
+ }
+
+ private TableNameOverride(String tableNameOverride, String tableNamePrefix) {
+ this.tableNameOverride = tableNameOverride;
+ this.tableNamePrefix = tableNamePrefix;
+ }
+
+ /**
+ * @see TableNameOverride#withTableNameReplacement(String)
+ */
+ public TableNameOverride(String tableNameOverride) {
+ this(tableNameOverride, null);
+ }
+
+ /**
+ * Returns the table name to use for all requests. Exclusive with
+ * {@link TableNameOverride#getTableNamePrefix()}
+ *
+ * @see DynamoDBMapperConfig#getTableNameOverride()
+ */
+ public String getTableName() {
+ return tableNameOverride;
+ }
+
+ /**
+ * Returns the table name prefix to prepend the table name for all
+ * requests. Exclusive with {@link TableNameOverride#getTableName()}
+ *
+ * @see DynamoDBMapperConfig#getTableNameOverride()
+ */
+ public String getTableNamePrefix() {
+ return tableNamePrefix;
+ }
+
+ public final DynamoDBMapperConfig config() {
+ return builder().withTableNameOverride(this).build();
+ }
+ }
+
+ /**
+ * Interface for a strategy used to determine the table name of an object based on its class.
+ * This resolver is used when an object isn't available such as in
+ * {@link DynamoDBMapper#query(Class, DynamoDBQueryExpression)}
+ *
+ * @see ObjectTableNameResolver
+ * @author Raniz
+ */
+ public static interface TableNameResolver {
+
+ /**
+ * Get the table name for a class. This method is used when an object is not available
+ * such as when creating requests for scan or query operations.
+ *
+ * @param clazz The class to get the table name for
+ * @param config The {@link DynamoDBMapperConfig}
+ * @return The table name to use for instances of clazz
+ */
+ public String getTableName(Class> clazz, DynamoDBMapperConfig config);
+ }
+
+ /**
+ * Interface for a strategy used to determine the table name of an object based on its class.
+ * This resolver is used when an object is available such as in
+ * {@link DynamoDBMapper#batchSave(Object...)}
+ *
+ * If no table name resolver for objects is set, {@link DynamoDBMapper} reverts to using the
+ * {@link TableNameResolver} on each object's class.
+ *
+ * @see TableNameResolver
+ * @author Raniz
+ */
+ public static interface ObjectTableNameResolver {
+
+ /**
+ * Get the table name for an object.
+ *
+ * @param object The object to get the table name for
+ * @param config The {@link DynamoDBMapperConfig}
+ * @return The table name to use for object
+ */
+ public String getTableName(Object object, DynamoDBMapperConfig config);
+
+ }
+
+ /**
+ * Default implementation of {@link TableNameResolver} that mimics the behavior
+ * of DynamoDBMapper before the addition of {@link TableNameResolver}.
+ *
+ * @author Raniz
+ */
+ public static class DefaultTableNameResolver implements TableNameResolver {
+ public static final DefaultTableNameResolver INSTANCE = new DefaultTableNameResolver();
+
+ @Override
+ public String getTableName(Class> clazz, DynamoDBMapperConfig config) {
+ final TableNameOverride override = config.getTableNameOverride();
+
+ if (override != null) {
+ final String tableName = override.getTableName();
+ if (tableName != null) {
+ return tableName;
+ }
+ }
+
+ final StandardBeanProperties.Beans> beans = StandardBeanProperties.of(clazz);
+ if (beans.properties().tableName() == null) {
+ throw new DynamoDBMappingException(clazz + " not annotated with @DynamoDBTable");
+ }
+
+ final String prefix = override == null ? null : override.getTableNamePrefix();
+ return prefix == null ? beans.properties().tableName() : prefix + beans.properties().tableName();
+ }
+
+ public final DynamoDBMapperConfig config() {
+ return builder().withTableNameResolver(this).build();
+ }
+ }
+
+ /**
+ * DynamoDBMapper#batchWrite takes arbitrary number of save/delete requests
+ * and breaks them into smaller chunks that can be accepted by the service
+ * API. Each chunk will be sent to DynamoDB via the BatchWriteItem API, and
+ * if it fails because the table's provisioned throughput is exceeded or an
+ * internal processing failure occurs, the failed requests are returned in
+ * the UnprocessedItems response parameter. This interface allows you to
+ * control the retry strategy when such scenario occurs.
+ *
+ * @see DynamoDBMapper#batchWrite(Iterable, Iterable, DynamoDBMapperConfig)
+ * @see DynamoDB service API reference -- BatchWriteItem
+ */
+ public interface BatchWriteRetryStrategy {
+
+ /**
+ * Returns the max number of retries to be performed if the service
+ * returns UnprocessedItems in the response.
+ *
+ * @param batchWriteItemInput
+ * the one batch of write requests that is being sent to the
+ * BatchWriteItem API.
+ * @return max number of retries to be performed if the service returns
+ * UnprocessedItems in the response, or a negative value if you
+ * want it to keep retrying until all the UnprocessedItems are
+ * fulfilled.
+ */
+ public int getMaxRetryOnUnprocessedItems(
+ Map> batchWriteItemInput);
+
+ /**
+ * Returns the delay (in milliseconds) before retrying on
+ * UnprocessedItems.
+ *
+ * @param unprocessedItems
+ * the UnprocessedItems returned by the service in the last
+ * BatchWriteItem call
+ * @param retriesAttempted
+ * The number of times we have attempted to resend
+ * UnprocessedItems.
+ * @return the delay (in milliseconds) before resending
+ * UnprocessedItems.
+ */
+ public long getDelayBeforeRetryUnprocessedItems(
+ Map> unprocessedItems,
+ int retriesAttempted);
+ }
+
+
+ /**
+ * {@link DynamoDBMapper#batchLoad(Iterable, DynamoDBMapperConfig)} breaks the requested items in batches of maximum size 100.
+ * When calling the Dynamo Db client, there is a chance that due to throttling, some unprocessed keys will be returned.
+ * This interfaces controls whether we need to retry these unprocessed keys and it also controls the strategy as to how retries should be handled
+ */
+ public interface BatchLoadRetryStrategy {
+ /**
+ * Checks if the batch load request should be retried.
+ * @param batchLoadContext see {@link BatchLoadContext}
+ *
+ * @return a boolean true or false value.
+ */
+ public boolean shouldRetry(final BatchLoadContext batchLoadContext);
+
+ /**
+ * Returns delay(in milliseconds) before retrying Unprocessed keys
+ *
+ * @param batchLoadContext see {@link BatchLoadContext}
+ * @return delay(in milliseconds) before attempting to read unprocessed keys
+ */
+ public long getDelayBeforeNextRetry(final BatchLoadContext batchLoadContext);
+ }
+
+ /**
+ * This strategy, like name suggests will not attempt any retries on Unprocessed keys
+ *
+ * @author smihir
+ *
+ */
+ public static class NoRetryBatchLoadRetryStrategy implements BatchLoadRetryStrategy {
+ public static final NoRetryBatchLoadRetryStrategy INSTANCE = new NoRetryBatchLoadRetryStrategy();
+
+ /* (non-Javadoc)
+ * @see software.amazon.awssdk.dynamodb.datamodeling.DynamoDBMapperConfig.BatchLoadRetryStrategy#getMaxRetryOnUnprocessedKeys(java.util.Map, java.util.Map)
+ */
+ @Override
+ public boolean shouldRetry(final BatchLoadContext batchLoadContext) {
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see software.amazon.awssdk.dynamodb.datamodeling.DynamoDBMapperConfig.BatchLoadRetryStrategy#getDelayBeforeNextRetry(java.util.Map, int)
+ */
+ @Override
+ public long getDelayBeforeNextRetry(final BatchLoadContext batchLoadContext) {
+ return -1;
+ }
+
+ public final DynamoDBMapperConfig config() {
+ return builder().withBatchLoadRetryStrategy(this).build();
+ }
+ }
+
+ /**
+ * This is the default strategy.
+ * If unprocessed keys is equal to requested keys, the request will retried 5 times with a back off strategy
+ * with maximum back off of 3 seconds
+ * If few of the keys have been processed, the retries happen without a delay.
+ *
+ * @author smihir
+ *
+ */
+ public static class DefaultBatchLoadRetryStrategy implements BatchLoadRetryStrategy {
+ public static final DefaultBatchLoadRetryStrategy INSTANCE = new DefaultBatchLoadRetryStrategy();
+
+ private static final int MAX_RETRIES = 5;
+ private static final long MAX_BACKOFF_IN_MILLISECONDS = 1000 * 3;
+
+ @Override
+ public long getDelayBeforeNextRetry(final BatchLoadContext batchLoadContext) {
+ Map requestedKeys = batchLoadContext.getBatchGetItemRequest().requestItems();
+ Map unprocessedKeys = batchLoadContext.getBatchGetItemResponse()
+ .unprocessedKeys();
+
+ long delay = 0;
+ //Exponential backoff only when all keys are unprocessed
+ if (unprocessedKeys != null && requestedKeys != null && unprocessedKeys.size() == requestedKeys.size()) {
+ Random random = new Random();
+ long scaleFactor = 500 + random.nextInt(100);
+ int retriesAttempted = batchLoadContext.getRetriesAttempted();
+ delay = (long) (Math.pow(2, retriesAttempted) * scaleFactor);
+ delay = Math.min(delay, MAX_BACKOFF_IN_MILLISECONDS);
+ }
+ return delay;
+ }
+
+ @Override
+ public boolean shouldRetry(BatchLoadContext batchLoadContext) {
+ Map unprocessedKeys = batchLoadContext.getBatchGetItemResponse().unprocessedKeys();
+ return (unprocessedKeys != null && unprocessedKeys.size() > 0 && batchLoadContext.getRetriesAttempted() < MAX_RETRIES);
+ }
+
+ public final DynamoDBMapperConfig config() {
+ return builder().withBatchLoadRetryStrategy(this).build();
+ }
+ }
+
+ /**
+ * The default BatchWriteRetryStrategy which always retries on
+ * UnprocessedItem up to a maximum number of times and use exponential
+ * backoff with random scale factor.
+ */
+ public static class DefaultBatchWriteRetryStrategy implements BatchWriteRetryStrategy {
+ public static final DefaultBatchWriteRetryStrategy INSTANCE = new DefaultBatchWriteRetryStrategy();
+
+ private static final long MAX_BACKOFF_IN_MILLISECONDS = 1000 * 3;
+ private static final int DEFAULT_MAX_RETRY = -1;
+
+ private final int maxRetry;
+
+ /**
+ * Keep retrying until success, with default backoff.
+ */
+ public DefaultBatchWriteRetryStrategy() {
+ this(DEFAULT_MAX_RETRY);
+ }
+
+ public DefaultBatchWriteRetryStrategy (int maxRetry) {
+ this.maxRetry = maxRetry;
+ }
+
+ @Override
+ public int getMaxRetryOnUnprocessedItems(
+ Map> batchWriteItemInput) {
+ return maxRetry;
+ }
+
+ @Override
+ public long getDelayBeforeRetryUnprocessedItems(
+ Map> unprocessedItems,
+ int retriesAttempted) {
+
+ if (retriesAttempted < 0) {
+ return 0;
+ }
+
+ Random random = new Random();
+ long scaleFactor = 1000 + random.nextInt(200);
+ long delay = (long) (Math.pow(2, retriesAttempted) * scaleFactor);
+ return Math.min(delay, MAX_BACKOFF_IN_MILLISECONDS);
+ }
+
+ public final DynamoDBMapperConfig config() {
+ return builder().withBatchWriteRetryStrategy(this).build();
+ }
+ }
+
+ private final SaveBehavior saveBehavior;
+ private final ConsistentReads consistentReads;
+ private final TableNameOverride tableNameOverride;
+ private final TableNameResolver tableNameResolver;
+ private final ObjectTableNameResolver objectTableNameResolver;
+ private final PaginationLoadingStrategy paginationLoadingStrategy;
+ private final ConversionSchema conversionSchema;
+ private final BatchWriteRetryStrategy batchWriteRetryStrategy;
+ private final BatchLoadRetryStrategy batchLoadRetryStrategy;
+ private final DynamoDBTypeConverterFactory typeConverterFactory;
+
+ /**
+ * Internal constructor; builds from the builder.
+ */
+ private DynamoDBMapperConfig(final DynamoDBMapperConfig.Builder builder) {
+ this.saveBehavior = builder.saveBehavior;
+ this.consistentReads = builder.consistentReads;
+ this.tableNameOverride = builder.tableNameOverride;
+ this.tableNameResolver = builder.tableNameResolver;
+ this.objectTableNameResolver = builder.objectTableNameResolver;
+ this.paginationLoadingStrategy = builder.paginationLoadingStrategy;
+ this.conversionSchema = builder.conversionSchema;
+ this.batchWriteRetryStrategy = builder.batchWriteRetryStrategy;
+ this.batchLoadRetryStrategy = builder.batchLoadRetryStrategy;
+ this.typeConverterFactory = builder.typeConverterFactory;
+ }
+
+ /**
+ * Merges these configuration values with the specified overrides; may
+ * simply return this instance if overrides are the same or null.
+ * @param overrides The overrides to merge.
+ * @return This if the overrides are same or null, or a new merged config.
+ */
+ final DynamoDBMapperConfig merge(final DynamoDBMapperConfig overrides) {
+ return overrides == null || this == overrides ? this : builder().merge(this).merge(overrides).build();
+ }
+
+ /**
+ * Legacy constructor, using default PaginationLoadingStrategy
+ * @deprecated in favor of the fluent {@link Builder}
+ * @see DynamoDBMapperConfig#builder()
+ **/
+ @Deprecated
+ public DynamoDBMapperConfig(
+ SaveBehavior saveBehavior,
+ ConsistentReads consistentReads,
+ TableNameOverride tableNameOverride) {
+
+ this(builder()
+ .withSaveBehavior(saveBehavior)
+ .withConsistentReads(consistentReads)
+ .withTableNameOverride(tableNameOverride));
+ }
+
+ /**
+ * Constructs a new configuration object with the save behavior, consistent
+ * read behavior, and table name override given.
+ *
+ * @param saveBehavior
+ * The {@link SaveBehavior} to use, or null for default.
+ * @param consistentReads
+ * The {@link ConsistentReads} to use, or null for default.
+ * @param tableNameOverride
+ * An override for the table name, or null for no override.
+ * @param paginationLoadingStrategy
+ * The pagination loading strategy, or null for default.
+ * @deprecated in favor of the fluent {@link Builder}
+ * @see DynamoDBMapperConfig#builder()
+ */
+ /**
+ * @deprecated in favor of the fluent {@link Builder}
+ * @see DynamoDBMapperConfig#builder()
+ */
+ @Deprecated
+ public DynamoDBMapperConfig(
+ SaveBehavior saveBehavior,
+ ConsistentReads consistentReads,
+ TableNameOverride tableNameOverride,
+ PaginationLoadingStrategy paginationLoadingStrategy) {
+
+ this(builder()
+ .withSaveBehavior(saveBehavior)
+ .withConsistentReads(consistentReads)
+ .withTableNameOverride(tableNameOverride)
+ .withPaginationLoadingStrategy(paginationLoadingStrategy));
+ }
+
+ @Deprecated
+ public DynamoDBMapperConfig(SaveBehavior saveBehavior) {
+ this(builder().withSaveBehavior(saveBehavior));
+ }
+
+ @Deprecated
+ public DynamoDBMapperConfig(ConsistentReads consistentReads) {
+ this(builder().withConsistentReads(consistentReads));
+ }
+
+ @Deprecated
+ public DynamoDBMapperConfig(TableNameOverride tableNameOverride) {
+ this(builder().withTableNameOverride(tableNameOverride));
+ }
+
+ @Deprecated
+ public DynamoDBMapperConfig(TableNameResolver tableNameResolver) {
+ this(builder().withTableNameResolver(tableNameResolver));
+ }
+
+ @Deprecated
+ public DynamoDBMapperConfig(ObjectTableNameResolver objectTableNameResolver) {
+ this(builder().withObjectTableNameResolver(objectTableNameResolver));
+ }
+
+ @Deprecated
+ public DynamoDBMapperConfig(TableNameResolver tableNameResolver, ObjectTableNameResolver objectTableNameResolver) {
+ this(builder().withTableNameResolver(tableNameResolver).withObjectTableNameResolver(objectTableNameResolver));
+ }
+
+ @Deprecated
+ public DynamoDBMapperConfig(PaginationLoadingStrategy paginationLoadingStrategy) {
+ this(builder().withPaginationLoadingStrategy(paginationLoadingStrategy));
+ }
+
+ @Deprecated
+ public DynamoDBMapperConfig(ConversionSchema conversionSchema) {
+ this(builder().withConversionSchema(conversionSchema));
+ }
+
+ @Deprecated
+ public DynamoDBMapperConfig(DynamoDBMapperConfig defaults, DynamoDBMapperConfig overrides) {
+ this(builder().merge(defaults).merge(overrides));
+ }
+
+ public BatchLoadRetryStrategy getBatchLoadRetryStrategy() {
+ return batchLoadRetryStrategy;
+ }
+
+ /**
+ * Returns the save behavior for this configuration.
+ */
+ public SaveBehavior getSaveBehavior() {
+ return saveBehavior;
+ }
+
+ /**
+ * Returns the consistent read behavior for this configuration.
+ */
+ public ConsistentReads getConsistentReads() {
+ return consistentReads;
+ }
+
+ /**
+ * Returns the table name override for this configuration. This value will
+ * override the table name specified in a {@link DynamoDBTable} annotation,
+ * either by replacing the table name entirely or else by pre-pending a
+ * string to each table name. This is useful for partitioning data in
+ * multiple tables at runtime.
+ *
+ * @see TableNameOverride#withTableNamePrefix(String)
+ * @see TableNameOverride#withTableNameReplacement(String)
+ */
+ public TableNameOverride getTableNameOverride() {
+ return tableNameOverride;
+ }
+
+ /**
+ * Returns the table name resolver for this configuration. This value will
+ * be used to determine the table name for classes. It can be
+ * used for more powerful customization of table name than is possible using
+ * only {@link TableNameOverride}.
+ *
+ * @see TableNameResolver#getTableName(Class, DynamoDBMapperConfig)
+ */
+ public TableNameResolver getTableNameResolver() {
+ return tableNameResolver;
+ }
+
+ /**
+ * Returns the object table name resolver for this configuration. This value will
+ * be used to determine the table name for objects. It can be
+ * used for more powerful customization of table name than is possible using
+ * only {@link TableNameOverride}.
+ *
+ * @see ObjectTableNameResolver#getTableName(Object, DynamoDBMapperConfig)
+ */
+ public ObjectTableNameResolver getObjectTableNameResolver() {
+ return objectTableNameResolver;
+ }
+
+ /**
+ * Returns the pagination loading strategy for this configuration.
+ */
+ public PaginationLoadingStrategy getPaginationLoadingStrategy() {
+ return paginationLoadingStrategy;
+ }
+
+ /**
+ * @return the conversion schema for this config object
+ */
+ public ConversionSchema getConversionSchema() {
+ return conversionSchema;
+ }
+
+ /**
+ * @return the BatchWriteRetryStrategy for this config object
+ */
+ public BatchWriteRetryStrategy getBatchWriteRetryStrategy() {
+ return batchWriteRetryStrategy;
+ }
+
+ /**
+ * @return the current type-converter factory
+ */
+ public final DynamoDBTypeConverterFactory getTypeConverterFactory() {
+ return typeConverterFactory;
+ }
+
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBMapperFieldModel.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBMapperFieldModel.java
new file mode 100644
index 000000000000..aca998487020
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBMapperFieldModel.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright 2011-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import static software.amazon.awssdk.dynamodb.datamodeling.DynamoDBAutoGenerateStrategy.ALWAYS;
+import static software.amazon.awssdk.dynamodb.datamodeling.StandardTypeConverters.Vector.LIST;
+import static software.amazon.awssdk.services.dynamodb.model.ComparisonOperator.BEGINS_WITH;
+import static software.amazon.awssdk.services.dynamodb.model.ComparisonOperator.BETWEEN;
+import static software.amazon.awssdk.services.dynamodb.model.ComparisonOperator.CONTAINS;
+import static software.amazon.awssdk.services.dynamodb.model.ComparisonOperator.EQ;
+import static software.amazon.awssdk.services.dynamodb.model.ComparisonOperator.GE;
+import static software.amazon.awssdk.services.dynamodb.model.ComparisonOperator.GT;
+import static software.amazon.awssdk.services.dynamodb.model.ComparisonOperator.IN;
+import static software.amazon.awssdk.services.dynamodb.model.ComparisonOperator.NULL;
+import static software.amazon.awssdk.services.dynamodb.model.ComparisonOperator.LE;
+import static software.amazon.awssdk.services.dynamodb.model.ComparisonOperator.LT;
+import static software.amazon.awssdk.services.dynamodb.model.ComparisonOperator.NE;
+import static software.amazon.awssdk.services.dynamodb.model.ComparisonOperator.NOT_CONTAINS;
+import static software.amazon.awssdk.services.dynamodb.model.ComparisonOperator.NOT_NULL;
+
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+import software.amazon.awssdk.services.dynamodb.model.ComparisonOperator;
+import software.amazon.awssdk.services.dynamodb.model.Condition;
+import software.amazon.awssdk.services.dynamodb.model.KeyType;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Field model.
+ *
+ * @param The object type.
+ * @param The field model type.
+ */
+public class DynamoDBMapperFieldModel implements DynamoDBAutoGenerator, DynamoDBTypeConverter {
+
+ public static enum DynamoDBAttributeType { B, N, S, BS, NS, SS, BOOL, NULL, L, M; };
+
+ private final DynamoDBMapperFieldModel.Properties properties;
+ private final DynamoDBTypeConverter converter;
+ private final DynamoDBAttributeType attributeType;
+ private final DynamoDBMapperFieldModel.Reflect reflect;
+
+ /**
+ * Creates a new field model instance.
+ * @param builder The builder.
+ */
+ private DynamoDBMapperFieldModel(final DynamoDBMapperFieldModel.Builder builder) {
+ this.properties = builder.properties;
+ this.converter = builder.converter;
+ this.attributeType = builder.attributeType;
+ this.reflect = builder.reflect;
+ }
+
+ /**
+ * @deprecated replaced by {@link DynamoDBMapperFieldModel#name}
+ */
+ @Deprecated
+ public String getDynamoDBAttributeName() {
+ return properties.attributeName();
+ }
+
+ /**
+ * @deprecated replaced by {@link DynamoDBMapperFieldModel#attributeType}
+ */
+ @Deprecated
+ public DynamoDBAttributeType getDynamoDBAttributeType() {
+ return attributeType;
+ }
+
+ /**
+ * Gets the attribute name.
+ * @return The attribute name.
+ */
+ public final String name() {
+ return properties.attributeName();
+ }
+
+ /**
+ * Gets the value from the object instance.
+ * @param object The object instance.
+ * @return The value.
+ */
+ public final V get(final T object) {
+ return reflect.get(object);
+ }
+
+ /**
+ * Sets the value on the object instance.
+ * @param object The object instance.
+ * @param value The value.
+ */
+ public final void set(final T object, final V value) {
+ reflect.set(object, value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final DynamoDBAutoGenerateStrategy getGenerateStrategy() {
+ if (properties.autoGenerator() != null) {
+ return properties.autoGenerator().getGenerateStrategy();
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final V generate(final V currentValue) {
+ return properties.autoGenerator().generate(currentValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final AttributeValue convert(final V object) {
+ return converter.convert(object);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final V unconvert(final AttributeValue object) {
+ return converter.unconvert(object);
+ }
+
+ /**
+ * Get the current value from the object and convert it.
+ * @param object The object instance.
+ * @return The converted value.
+ */
+ public final AttributeValue getAndConvert(final T object) {
+ return convert(get(object));
+ }
+
+ /**
+ * Unconverts the value and sets it on the object.
+ * @param object The object instance.
+ * @param value The attribute value.
+ */
+ public final void unconvertAndSet(final T object, final AttributeValue value) {
+ set(object, unconvert(value));
+ }
+
+ /**
+ * Gets the DynamoDB attribute type.
+ * @return The DynamoDB attribute type.
+ */
+ public final DynamoDBAttributeType attributeType() {
+ return attributeType;
+ }
+
+ /**
+ * Gets the key type.
+ * @return The key type if a key field, null otherwise.
+ */
+ public final KeyType keyType() {
+ return properties.keyType();
+ }
+
+ /**
+ * Indicates if this attribute is a version attribute.
+ * @return True if it is, false otherwise.
+ */
+ public final boolean versioned() {
+ return properties.versioned();
+ }
+
+ /**
+ * Gets the global secondary indexes.
+ * @param keyType The key type.
+ * @return The list of global secondary indexes.
+ */
+ public final List globalSecondaryIndexNames(final KeyType keyType) {
+ if (properties.globalSecondaryIndexNames().containsKey(keyType)) {
+ return properties.globalSecondaryIndexNames().get(keyType);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Gets the local secondary indexes.
+ * @return The list of local secondary indexes.
+ */
+ public final List localSecondaryIndexNames() {
+ return properties.localSecondaryIndexNames();
+ }
+
+ /**
+ * Returns true if the field has any indexes.
+ * @return True if the propery matches.
+ */
+ public final boolean indexed() {
+ return !properties.globalSecondaryIndexNames().isEmpty() || !properties.localSecondaryIndexNames().isEmpty();
+ }
+
+ /**
+ * Creates a condition which filters on the specified value.
+ * @param value The value.
+ * @return The condition.
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#BEGINS_WITH
+ * @see software.amazon.awssdk.services.dynamodb.model.Condition
+ */
+ public final Condition beginsWith(final V value) {
+ return Condition.builder().comparisonOperator(BEGINS_WITH).attributeValueList(convert(value)).build();
+ }
+
+ /**
+ * Creates a condition which filters on the specified values.
+ * @param lo The start of the range (inclusive).
+ * @param hi The end of the range (inclusive).
+ * @return The condition.
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#BETWEEN
+ * @see software.amazon.awssdk.services.dynamodb.model.Condition
+ */
+ public final Condition between(final V lo, final V hi) {
+ return Condition.builder().comparisonOperator(BETWEEN).attributeValueList(convert(lo), convert(hi)).build();
+ }
+
+ /**
+ * Creates a condition which filters on the specified value.
+ * @param value The value.
+ * @return The condition.
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#CONTAINS
+ * @see software.amazon.awssdk.services.dynamodb.model.Condition
+ */
+ public final Condition contains(final V value) {
+ return Condition.builder().comparisonOperator(CONTAINS).attributeValueList(convert(value)).build();
+ }
+
+ /**
+ * Creates a condition which filters on the specified value.
+ * @param value The value.
+ * @return The condition.
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#EQ
+ * @see software.amazon.awssdk.services.dynamodb.model.Condition
+ */
+ public final Condition eq(final V value) {
+ return Condition.builder().comparisonOperator(EQ).attributeValueList(convert(value)).build();
+ }
+
+ /**
+ * Creates a condition which filters on the specified value.
+ * @param value The value.
+ * @return The condition.
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#GE
+ * @see software.amazon.awssdk.services.dynamodb.model.Condition
+ */
+ public final Condition ge(final V value) {
+ return Condition.builder().comparisonOperator(GE).attributeValueList(convert(value)).build();
+ }
+
+ /**
+ * Creates a condition which filters on the specified value.
+ * @param value The value.
+ * @return The condition.
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#GT
+ * @see software.amazon.awssdk.services.dynamodb.model.Condition
+ */
+ public final Condition gt(final V value) {
+ return Condition.builder().comparisonOperator(GT).attributeValueList(convert(value)).build();
+ }
+
+ /**
+ * Creates a condition which filters on the specified values.
+ * @param values The values.
+ * @return The condition.
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#IN
+ * @see software.amazon.awssdk.services.dynamodb.model.Condition
+ */
+ public final Condition in(final Collection values) {
+ return Condition.builder().comparisonOperator(IN).attributeValueList(LIST.convert(values, this)).build();
+ }
+
+ /**
+ * Creates a condition which filters on the specified values.
+ * @param values The values.
+ * @return The condition.
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#IN
+ * @see software.amazon.awssdk.services.dynamodb.model.Condition
+ */
+ public final Condition in(final V ... values) {
+ return in(Arrays.asList(values));
+ }
+
+ /**
+ * Creates a condition which filters on the specified value.
+ * @return The condition.
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#NULL
+ * @see software.amazon.awssdk.services.dynamodb.model.Condition
+ */
+ public final Condition isNull() {
+ return Condition.builder().comparisonOperator(NULL).build();
+ }
+
+ /**
+ * Creates a condition which filters on the specified value.
+ * @param value The value.
+ * @return The condition.
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#LE
+ * @see software.amazon.awssdk.services.dynamodb.model.Condition
+ */
+ public final Condition le(final V value) {
+ return Condition.builder().comparisonOperator(LE).attributeValueList(convert(value)).build();
+ }
+
+ /**
+ * Creates a condition which filters on the specified value.
+ * @param value The value.
+ * @return The condition.
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#LT
+ * @see software.amazon.awssdk.services.dynamodb.model.Condition
+ */
+ public final Condition lt(final V value) {
+ return Condition.builder().comparisonOperator(LT).attributeValueList(convert(value)).build();
+ }
+
+ /**
+ * Creates a condition which filters on the specified value.
+ * @param value The value.
+ * @return The condition.
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#NE
+ * @see software.amazon.awssdk.services.dynamodb.model.Condition
+ */
+ public final Condition ne(final V value) {
+ return Condition.builder().comparisonOperator(NE).attributeValueList(convert(value)).build();
+ }
+
+ /**
+ * Creates a condition which filters on the specified value.
+ * @param value The value.
+ * @return The condition.
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#NOT_CONTAINS
+ * @see software.amazon.awssdk.services.dynamodb.model.Condition
+ */
+ public final Condition notContains(final V value) {
+ return Condition.builder().comparisonOperator(NOT_CONTAINS).attributeValueList(convert(value)).build();
+ }
+
+ /**
+ * Creates a condition which filters on the specified value.
+ * @return The condition.
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#NOT_NULL
+ * @see software.amazon.awssdk.services.dynamodb.model.Condition
+ */
+ public final Condition notNull() {
+ return Condition.builder().comparisonOperator(NOT_NULL).build();
+ }
+
+ /**
+ * Creates a condition which filters on any non-null argument; if {@code lo}
+ * is null a {@code LE} condition is applied on {@code hi}, if {@code hi}
+ * is null a {@code GE} condition is applied on {@code lo}.
+ * @param lo The start of the range (inclusive).
+ * @param hi The end of the range (inclusive).
+ * @return The condition or null if both arguments are null.
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#BETWEEN
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#EQ
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#GE
+ * @see software.amazon.awssdk.services.dynamodb.model.ComparisonOperator#LE
+ * @see software.amazon.awssdk.services.dynamodb.model.Condition
+ */
+ public final Condition betweenAny(final V lo, final V hi) {
+ return lo == null ? (hi == null ? null : le(hi)) : (hi == null ? ge(lo) : (lo.equals(hi) ? eq(lo) : between(lo,hi)));
+ }
+
+ /**
+ * {@link DynamoDBMapperFieldModel} builder.
+ */
+ static class Builder {
+ private final DynamoDBMapperFieldModel.Properties properties;
+ private DynamoDBTypeConverter converter;
+ private DynamoDBMapperFieldModel.Reflect reflect;
+ private DynamoDBAttributeType attributeType;
+ private Class targetType;
+
+ public Builder(Class targetType, DynamoDBMapperFieldModel.Properties properties) {
+ this.properties = properties;
+ this.targetType = targetType;
+ }
+
+ public final Builder with(DynamoDBTypeConverter converter) {
+ this.converter = converter;
+ return this;
+ }
+
+ public final Builder with(DynamoDBAttributeType attributeType) {
+ this.attributeType = attributeType;
+ return this;
+ }
+
+ public final Builder with(DynamoDBMapperFieldModel.Reflect reflect) {
+ this.reflect = reflect;
+ return this;
+ }
+
+ public final DynamoDBMapperFieldModel build() {
+ final DynamoDBMapperFieldModel result = new DynamoDBMapperFieldModel(this);
+ if ((result.keyType() != null || result.indexed()) && !result.attributeType().name().matches("[BNS]")) {
+ throw new DynamoDBMappingException(String.format(
+ "%s[%s]; only scalar (B, N, or S) type allowed for key",
+ targetType.getSimpleName(), result.name()
+ ));
+ } else if (result.keyType() != null && result.getGenerateStrategy() == ALWAYS) {
+ throw new DynamoDBMappingException(String.format(
+ "%s[%s]; auto-generated key and ALWAYS not allowed",
+ targetType.getSimpleName(), result.name()
+ ));
+ }
+ return result;
+ }
+ }
+
+ /**
+ * The field model properties.
+ */
+ static interface Properties {
+ public String attributeName();
+ public KeyType keyType();
+ public boolean versioned();
+ public Map> globalSecondaryIndexNames();
+ public List localSecondaryIndexNames();
+ public DynamoDBAutoGenerator autoGenerator();
+
+ static final class Immutable implements Properties {
+ private final String attributeName;
+ private final KeyType keyType;
+ private final boolean versioned;
+ private final Map> globalSecondaryIndexNames;
+ private final List localSecondaryIndexNames;
+ private final DynamoDBAutoGenerator autoGenerator;
+
+ public Immutable(final Properties properties) {
+ this.attributeName = properties.attributeName();
+ this.keyType = properties.keyType();
+ this.versioned = properties.versioned();
+ this.globalSecondaryIndexNames = properties.globalSecondaryIndexNames();
+ this.localSecondaryIndexNames = properties.localSecondaryIndexNames();
+ this.autoGenerator = properties.autoGenerator();
+ }
+
+ @Override
+ public final String attributeName() {
+ return this.attributeName;
+ }
+
+ @Override
+ public final KeyType keyType() {
+ return this.keyType;
+ }
+
+ @Override
+ public final boolean versioned() {
+ return this.versioned;
+ }
+
+ @Override
+ public final Map> globalSecondaryIndexNames() {
+ return this.globalSecondaryIndexNames;
+ }
+
+ @Override
+ public final List localSecondaryIndexNames() {
+ return this.localSecondaryIndexNames;
+ }
+
+ @Override
+ public final DynamoDBAutoGenerator autoGenerator() {
+ return this.autoGenerator;
+ }
+ }
+ }
+
+ /**
+ * Get/set reflection operations.
+ * @param The object type.
+ * @param The value type.
+ */
+ static interface Reflect {
+ public V get(T object);
+ public void set(T object, V value);
+ }
+
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBMapperModelFactory.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBMapperModelFactory.java
new file mode 100644
index 000000000000..e96e40a14512
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBMapperModelFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2016-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+
+/**
+ * {@link DynamoDBMapper} table model factory.
+ */
+public interface DynamoDBMapperModelFactory {
+
+ /**
+ * Gets/creates the mapper's model factory.
+ */
+ public TableFactory getTableFactory(DynamoDBMapperConfig config);
+
+ /**
+ * {@link DynamoDBMapperModelFactory} factory.
+ */
+ public static interface TableFactory {
+ /**
+ * Gets the table model for the given type and configuration.
+ */
+ public DynamoDBMapperTableModel getTable(Class clazz);
+ }
+
+}
diff --git a/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBMapperTableModel.java b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBMapperTableModel.java
new file mode 100644
index 000000000000..d03ac64e50a3
--- /dev/null
+++ b/test/dynamodb-mapper-v2/src/main/java/software/amazon/awssdk/dynamodb/datamodeling/DynamoDBMapperTableModel.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright 2016-2025 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.
+ * You may obtain a copy of the License at:
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * 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.dynamodb.datamodeling;
+
+import static software.amazon.awssdk.services.dynamodb.model.KeyType.HASH;
+import static software.amazon.awssdk.services.dynamodb.model.KeyType.RANGE;
+import static software.amazon.awssdk.services.dynamodb.model.ProjectionType.KEYS_ONLY;
+
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+import software.amazon.awssdk.services.dynamodb.model.GlobalSecondaryIndex;
+import software.amazon.awssdk.services.dynamodb.model.KeyType;
+import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
+import software.amazon.awssdk.services.dynamodb.model.LocalSecondaryIndex;
+import software.amazon.awssdk.services.dynamodb.model.Projection;
+import software.amazon.awssdk.services.dynamodb.model.ProjectionType;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+
+/**
+ * Table model.
+ *
+ * @param The object type.
+ */
+public final class DynamoDBMapperTableModel implements DynamoDBTypeConverter