This is an automated email from the ASF dual-hosted git repository.

ferenc-csaky pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/flink-connector-aws.git


The following commit(s) were added to refs/heads/main by this push:
     new fc3a8c9d [FLINK-40938] Support `AT_TIMESTAMP` initial position in 
DynamoDB Streams source
fc3a8c9d is described below

commit fc3a8c9de287482ed4575a4cd3528228aaf7c6a4
Author: riyarawat-amazon <[email protected]>
AuthorDate: Thu Oct 29 15:26:39 2026 +0530

    [FLINK-40938] Support `AT_TIMESTAMP` initial position in DynamoDB Streams 
source
---
 .../content/docs/connectors/datastream/dynamodb.md |  18 ++
 .../sink/KinesisFirehoseSinkWriterTest.java        |   2 +-
 .../kinesis/sink/KinesisStreamsSinkWriterTest.java |   2 +-
 .../dynamodb/source/DynamoDbStreamsSource.java     |  13 +
 .../DynamodbStreamsSourceConfigConstants.java      |  20 +-
 .../config/DynamodbStreamsSourceConfigUtil.java    | 146 +++++++++++
 .../DynamoDbStreamsSourceEnumerator.java           |   9 +-
 .../source/enumerator/tracker/SplitTracker.java    | 105 +++++++-
 .../source/proxy/DynamoDbStreamsProxy.java         |  11 +
 .../split/DynamoDbStreamsShardSplitSerializer.java |  17 +-
 .../dynamodb/source/split/StartingPosition.java    |   5 +
 .../DynamodbStreamsSourceConfigUtilTest.java       | 220 +++++++++++++++++
 .../DynamoDbStreamsSourceEnumeratorTest.java       | 269 +++++++++++++++++++++
 .../enumerator/tracker/SplitTrackerTest.java       | 216 +++++++++++++++++
 .../source/proxy/DynamoDbStreamsProxyTest.java     |  39 +++
 .../DynamoDbStreamsShardSplitSerializerTest.java   |  30 ++-
 .../connector/sqs/sink/SqsSinkWriterTest.java      |   2 +-
 .../pom.xml                                        |   3 +
 .../src/main/resources/META-INF/NOTICE             |  58 ++---
 .../pom.xml                                        |   3 +
 .../src/main/resources/META-INF/NOTICE             |  62 ++---
 .../flink-sql-connector-dynamodb/pom.xml           |   3 +
 .../src/main/resources/META-INF/NOTICE             |  60 ++---
 pom.xml                                            |   2 +-
 24 files changed, 1207 insertions(+), 108 deletions(-)

diff --git a/docs/content/docs/connectors/datastream/dynamodb.md 
b/docs/content/docs/connectors/datastream/dynamodb.md
index 4a738034..7d639e70 100644
--- a/docs/content/docs/connectors/datastream/dynamodb.md
+++ b/docs/content/docs/connectors/datastream/dynamodb.md
@@ -118,6 +118,24 @@ The above is a simple example of using the 
`DynamoDbStreamsSource`.
 To specify the starting position of the `DynamodbStreamsSource`, users can set 
the `DynamodbStreamsSourceConfigConstants.STREAM_INITIAL_POSITION` in 
configuration.
 - `LATEST`: read all shards of the stream starting from the latest record.
 - `TRIM_HORIZON`: read all shards of the stream starting from the earliest 
record possible (data is trimmed by DynamoDB after 24 hours).
+- `AT_TIMESTAMP`: read all shards of the stream starting from the first record 
at or after a given timestamp.
+  The timestamp is set with 
`DynamodbStreamsSourceConfigConstants.STREAM_INITIAL_TIMESTAMP`, either as a 
date string in the
+  format given by 
`DynamodbStreamsSourceConfigConstants.STREAM_INITIAL_TIMESTAMP_FORMAT` (default 
`yyyy-MM-dd'T'HH:mm:ss.SSSXXX`)
+  or as epoch seconds. The timestamp must not be in the future. A timestamp 
older than the stream's retention starts each
+  shard from its earliest available record, like `TRIM_HORIZON`.
+
+{{< tabs "dynamodb-streams-source-at-timestamp" >}}
+{{< tab "Java" >}}
+```java
+Configuration sourceConfig = new Configuration();
+sourceConfig.set(DynamodbStreamsSourceConfigConstants.STREAM_INITIAL_POSITION, 
DynamodbStreamsSourceConfigConstants.InitialPosition.AT_TIMESTAMP);
+sourceConfig.set(DynamodbStreamsSourceConfigConstants.STREAM_INITIAL_TIMESTAMP,
 "2024-05-01T12:00:00.000Z");
+```
+{{< /tab >}}
+{{< /tabs >}}
+
+The starting position applies only when the job starts without state. When a 
job is restored from a checkpoint or savepoint,
+each shard resumes from its checkpointed position and the configured timestamp 
is not used.
 
 ### Deserialization Schema
 
diff --git 
a/flink-connector-aws/flink-connector-aws-kinesis-firehose/src/test/java/org/apache/flink/connector/firehose/sink/KinesisFirehoseSinkWriterTest.java
 
b/flink-connector-aws/flink-connector-aws-kinesis-firehose/src/test/java/org/apache/flink/connector/firehose/sink/KinesisFirehoseSinkWriterTest.java
index 2d9ddefe..6d686aa2 100644
--- 
a/flink-connector-aws/flink-connector-aws-kinesis-firehose/src/test/java/org/apache/flink/connector/firehose/sink/KinesisFirehoseSinkWriterTest.java
+++ 
b/flink-connector-aws/flink-connector-aws-kinesis-firehose/src/test/java/org/apache/flink/connector/firehose/sink/KinesisFirehoseSinkWriterTest.java
@@ -50,7 +50,7 @@ public class KinesisFirehoseSinkWriterTest {
     @BeforeEach
     void setup() throws IOException {
         TestSinkInitContext sinkInitContext = new TestSinkInitContext();
-        Properties sinkProperties = 
AWSServicesTestUtils.createConfig("https://fake_aws_endpoint";);
+        Properties sinkProperties = 
AWSServicesTestUtils.createConfig("https://fake-aws-endpoint";);
         KinesisFirehoseSink<String> sink =
                 new KinesisFirehoseSink<>(
                         ELEMENT_CONVERTER_PLACEHOLDER,
diff --git 
a/flink-connector-aws/flink-connector-aws-kinesis-streams/src/test/java/org/apache/flink/connector/kinesis/sink/KinesisStreamsSinkWriterTest.java
 
b/flink-connector-aws/flink-connector-aws-kinesis-streams/src/test/java/org/apache/flink/connector/kinesis/sink/KinesisStreamsSinkWriterTest.java
index f3c13d42..2fb4d236 100644
--- 
a/flink-connector-aws/flink-connector-aws-kinesis-streams/src/test/java/org/apache/flink/connector/kinesis/sink/KinesisStreamsSinkWriterTest.java
+++ 
b/flink-connector-aws/flink-connector-aws-kinesis-streams/src/test/java/org/apache/flink/connector/kinesis/sink/KinesisStreamsSinkWriterTest.java
@@ -58,7 +58,7 @@ public class KinesisStreamsSinkWriterTest {
     void 
testCreateKinesisStreamsSinkWriterInitializesRateLimitingStrategyWithExpectedParameters()
             throws IOException {
         TestSinkInitContext sinkInitContext = new TestSinkInitContext();
-        Properties sinkProperties = 
AWSServicesTestUtils.createConfig("https://fake_aws_endpoint";);
+        Properties sinkProperties = 
AWSServicesTestUtils.createConfig("https://fake-aws-endpoint";);
         KinesisStreamsSink<String> sink =
                 new KinesisStreamsSink<>(
                         ELEMENT_CONVERTER_PLACEHOLDER,
diff --git 
a/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/DynamoDbStreamsSource.java
 
b/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/DynamoDbStreamsSource.java
index 1581c9da..96c5eb0d 100644
--- 
a/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/DynamoDbStreamsSource.java
+++ 
b/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/DynamoDbStreamsSource.java
@@ -32,6 +32,7 @@ import org.apache.flink.connector.aws.util.AWSClientUtil;
 import org.apache.flink.connector.aws.util.AWSGeneralUtil;
 import 
org.apache.flink.connector.base.source.reader.fetcher.SingleThreadFetcherManager;
 import 
org.apache.flink.connector.dynamodb.source.config.DynamodbStreamsSourceConfigConstants;
+import 
org.apache.flink.connector.dynamodb.source.config.DynamodbStreamsSourceConfigUtil;
 import 
org.apache.flink.connector.dynamodb.source.enumerator.DynamoDbStreamsShardAssigner;
 import 
org.apache.flink.connector.dynamodb.source.enumerator.DynamoDbStreamsSourceEnumerator;
 import 
org.apache.flink.connector.dynamodb.source.enumerator.DynamoDbStreamsSourceEnumeratorState;
@@ -60,7 +61,10 @@ import software.amazon.awssdk.services.dynamodb.model.Shard;
 import software.amazon.awssdk.services.dynamodb.streams.DynamoDbStreamsClient;
 import software.amazon.awssdk.utils.AttributeMap;
 
+import javax.annotation.Nullable;
+
 import java.time.Duration;
+import java.time.Instant;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
@@ -100,6 +104,12 @@ public class DynamoDbStreamsSource<T>
     protected final DynamoDbStreamsDeserializationSchema<T> 
deserializationSchema;
     protected final DynamoDbStreamsShardAssigner dynamoDbStreamsShardAssigner;
 
+    /**
+     * The AT_TIMESTAMP initial timestamp, resolved and validated once when 
the source is built, so
+     * the enumerator uses exactly the value that was validated. Null for 
other initial positions.
+     */
+    @Nullable private final Instant initialTimestamp;
+
     DynamoDbStreamsSource(
             String streamArn,
             Configuration sourceConfig,
@@ -110,6 +120,8 @@ public class DynamoDbStreamsSource<T>
         Preconditions.checkNotNull(sourceConfig);
         Preconditions.checkNotNull(deserializationSchema);
         Preconditions.checkNotNull(dynamoDbStreamsShardAssigner);
+        this.initialTimestamp =
+                
DynamodbStreamsSourceConfigUtil.validateStreamSourceConfiguration(sourceConfig);
         this.streamArn = streamArn;
         this.sourceConfig = sourceConfig;
         this.deserializationSchema = deserializationSchema;
@@ -203,6 +215,7 @@ public class DynamoDbStreamsSource<T>
                 enumContext,
                 streamArn,
                 sourceConfig,
+                initialTimestamp,
                 createDynamoDbStreamsProxy(sourceConfig, 
adaptiveRetryStrategy),
                 dynamoDbStreamsShardAssigner,
                 checkpoint);
diff --git 
a/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/config/DynamodbStreamsSourceConfigConstants.java
 
b/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/config/DynamodbStreamsSourceConfigConstants.java
index 94b02b86..2ee97fa5 100644
--- 
a/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/config/DynamodbStreamsSourceConfigConstants.java
+++ 
b/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/config/DynamodbStreamsSourceConfigConstants.java
@@ -30,7 +30,8 @@ public class DynamodbStreamsSourceConfigConstants {
     /** Marks the initial position to use when reading from the Dynamodb 
stream. */
     public enum InitialPosition {
         LATEST,
-        TRIM_HORIZON
+        TRIM_HORIZON,
+        AT_TIMESTAMP
     }
 
     public static final ConfigOption<InitialPosition> STREAM_INITIAL_POSITION =
@@ -39,6 +40,23 @@ public class DynamodbStreamsSourceConfigConstants {
                     .defaultValue(InitialPosition.LATEST)
                     .withDescription("The initial position to start reading 
Dynamodb streams.");
 
+    public static final ConfigOption<String> STREAM_INITIAL_TIMESTAMP =
+            ConfigOptions.key("flink.stream.initpos.timestamp")
+                    .stringType()
+                    .noDefaultValue()
+                    .withDescription(
+                            "The initial timestamp at which to start reading 
from the DynamoDB stream. "
+                                    + "Used only when the initial position is 
AT_TIMESTAMP.");
+
+    public static final ConfigOption<String> STREAM_INITIAL_TIMESTAMP_FORMAT =
+            ConfigOptions.key("flink.stream.initpos.timestamp.format")
+                    .stringType()
+                    .defaultValue("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
+                    .withDescription(
+                            "The date format of the initial timestamp. Used to 
parse "
+                                    + "flink.stream.initpos.timestamp when it 
is not an epoch-second "
+                                    + "value. Used only when the initial 
position is AT_TIMESTAMP.");
+
     public static final ConfigOption<Duration> SHARD_DISCOVERY_INTERVAL =
             ConfigOptions.key("flink.shard.discovery.intervalmillis")
                     .durationType()
diff --git 
a/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/config/DynamodbStreamsSourceConfigUtil.java
 
b/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/config/DynamodbStreamsSourceConfigUtil.java
new file mode 100644
index 00000000..87328751
--- /dev/null
+++ 
b/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/config/DynamodbStreamsSourceConfigUtil.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.flink.connector.dynamodb.source.config;
+
+import org.apache.flink.annotation.Internal;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.util.Preconditions;
+
+import javax.annotation.Nullable;
+
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.util.Date;
+
+import static 
org.apache.flink.connector.dynamodb.source.config.DynamodbStreamsSourceConfigConstants.STREAM_INITIAL_POSITION;
+import static 
org.apache.flink.connector.dynamodb.source.config.DynamodbStreamsSourceConfigConstants.STREAM_INITIAL_TIMESTAMP;
+import static 
org.apache.flink.connector.dynamodb.source.config.DynamodbStreamsSourceConfigConstants.STREAM_INITIAL_TIMESTAMP_FORMAT;
+
+/** Utility functions to use with {@link 
DynamodbStreamsSourceConfigConstants}. */
+@Internal
+public class DynamodbStreamsSourceConfigUtil {
+
+    private DynamodbStreamsSourceConfigUtil() {
+        // private constructor to prevent initialization of utility class.
+    }
+
+    /**
+     * Parses the timestamp at which to start reading, used for the 
AT_TIMESTAMP initial position.
+     * The value is parsed strictly (non-lenient, whole string) using the 
configured {@link
+     * DynamodbStreamsSourceConfigConstants#STREAM_INITIAL_TIMESTAMP_FORMAT}; 
if that fails it is
+     * interpreted as a non-negative, possibly fractional, epoch-second value.
+     *
+     * @param sourceConfig the configuration to parse the timestamp from
+     * @return the parsed initial timestamp
+     */
+    public static Instant parseInitialTimestamp(Configuration sourceConfig) {
+        String timestampString = sourceConfig.get(STREAM_INITIAL_TIMESTAMP);
+        if (timestampString == null) {
+            throw new IllegalArgumentException(
+                    STREAM_INITIAL_TIMESTAMP.key()
+                            + " must be set when "
+                            + STREAM_INITIAL_POSITION.key()
+                            + " is AT_TIMESTAMP.");
+        }
+        String format = sourceConfig.get(STREAM_INITIAL_TIMESTAMP_FORMAT);
+        Instant parsed = parseWithFormat(timestampString, format);
+        if (parsed != null) {
+            return parsed;
+        }
+        // Fall back to interpreting the value as epoch seconds (may be 
fractional).
+        final double epochSeconds;
+        try {
+            epochSeconds = Double.parseDouble(timestampString);
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException(
+                    "Unable to parse "
+                            + STREAM_INITIAL_TIMESTAMP.key()
+                            + " value '"
+                            + timestampString
+                            + "' using format '"
+                            + format
+                            + "' or as epoch seconds.",
+                    e);
+        }
+        if (!Double.isFinite(epochSeconds) || epochSeconds < 0) {
+            throw new IllegalArgumentException(
+                    STREAM_INITIAL_TIMESTAMP.key()
+                            + " value '"
+                            + timestampString
+                            + "' must be a date in format '"
+                            + format
+                            + "' or a non-negative epoch-second value.");
+        }
+        return Instant.ofEpochMilli((long) (epochSeconds * 1000));
+    }
+
+    /** Parses the whole string with the format, non-leniently; null if it 
does not match. */
+    @Nullable
+    private static Instant parseWithFormat(String timestampString, String 
format) {
+        final SimpleDateFormat dateFormat;
+        try {
+            dateFormat = new SimpleDateFormat(format);
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+        dateFormat.setLenient(false);
+        ParsePosition position = new ParsePosition(0);
+        Date date = dateFormat.parse(timestampString, position);
+        if (date == null || position.getIndex() != timestampString.length()) {
+            return null;
+        }
+        return date.toInstant();
+    }
+
+    /**
+     * Validates the source configuration, failing fast at startup so that a 
misconfigured job does
+     * not fail later during shard discovery or reading. When the initial 
position is AT_TIMESTAMP,
+     * the timestamp must be present, parseable, and not in the future: a 
future timestamp is
+     * rejected by GetShardIterator, which would otherwise make the job 
restart repeatedly until the
+     * clock passes it.
+     *
+     * @param sourceConfig the configuration to validate
+     * @return the validated initial timestamp when the initial position is 
AT_TIMESTAMP, otherwise
+     *     null. The source passes this value to the enumerator, so the 
timestamp that was validated
+     *     is the one used.
+     */
+    @Nullable
+    public static Instant validateStreamSourceConfiguration(Configuration 
sourceConfig) {
+        Preconditions.checkNotNull(sourceConfig, "Config cannot be null");
+        if 
(DynamodbStreamsSourceConfigConstants.InitialPosition.AT_TIMESTAMP.equals(
+                sourceConfig.get(STREAM_INITIAL_POSITION))) {
+            // Parsing throws for a missing or invalid timestamp, surfacing 
the error at startup.
+            final Instant timestamp = parseInitialTimestamp(sourceConfig);
+            final Instant now = Instant.now();
+            if (timestamp.isAfter(now)) {
+                throw new IllegalArgumentException(
+                        STREAM_INITIAL_TIMESTAMP.key()
+                                + " "
+                                + timestamp
+                                + " is in the future (current time "
+                                + now
+                                + "). A future timestamp is not allowed; 
supply a timestamp at or"
+                                + " before the current time.");
+            }
+            return timestamp;
+        }
+        return null;
+    }
+}
diff --git 
a/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/enumerator/DynamoDbStreamsSourceEnumerator.java
 
b/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/enumerator/DynamoDbStreamsSourceEnumerator.java
index 0fb53907..90f40f14 100644
--- 
a/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/enumerator/DynamoDbStreamsSourceEnumerator.java
+++ 
b/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/enumerator/DynamoDbStreamsSourceEnumerator.java
@@ -33,6 +33,7 @@ import 
org.apache.flink.connector.dynamodb.source.exception.DynamoDbStreamsSourc
 import org.apache.flink.connector.dynamodb.source.proxy.StreamProxy;
 import 
org.apache.flink.connector.dynamodb.source.split.DynamoDbStreamsShardSplit;
 import org.apache.flink.connector.dynamodb.source.util.ListShardsResult;
+import org.apache.flink.util.Preconditions;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -82,6 +83,7 @@ public class DynamoDbStreamsSourceEnumerator
             SplitEnumeratorContext<DynamoDbStreamsShardSplit> context,
             String streamArn,
             Configuration sourceConfig,
+            @Nullable Instant initialTimestamp,
             StreamProxy streamProxy,
             DynamoDbStreamsShardAssigner shardAssigner,
             DynamoDbStreamsSourceEnumeratorState state) {
@@ -93,7 +95,12 @@ public class DynamoDbStreamsSourceEnumerator
         this.shardAssignerContext = new ShardAssignerContext(splitAssignment, 
context);
         InitialPosition initialPosition = 
sourceConfig.get(STREAM_INITIAL_POSITION);
         if (state == null) {
-            this.startTimestamp = Instant.now();
+            this.startTimestamp =
+                    initialPosition == InitialPosition.AT_TIMESTAMP
+                            ? Preconditions.checkNotNull(
+                                    initialTimestamp,
+                                    "initialTimestamp must be set when the 
initial position is AT_TIMESTAMP")
+                            : Instant.now();
             this.splitTracker = new SplitTracker(streamArn, initialPosition, 
this.startTimestamp);
         } else {
             this.splitTracker =
diff --git 
a/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/enumerator/tracker/SplitTracker.java
 
b/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/enumerator/tracker/SplitTracker.java
index 38fdf16c..8a8363f6 100644
--- 
a/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/enumerator/tracker/SplitTracker.java
+++ 
b/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/enumerator/tracker/SplitTracker.java
@@ -33,6 +33,7 @@ import org.slf4j.LoggerFactory;
 import software.amazon.awssdk.services.dynamodb.model.Shard;
 
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -43,6 +44,7 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
+import static 
org.apache.flink.connector.dynamodb.source.config.DynamodbStreamsSourceConfigConstants.InitialPosition.AT_TIMESTAMP;
 import static 
org.apache.flink.connector.dynamodb.source.config.DynamodbStreamsSourceConfigConstants.InitialPosition.LATEST;
 import static 
org.apache.flink.connector.dynamodb.source.config.DynamodbStreamsSourceConfigConstants.InitialPosition.TRIM_HORIZON;
 import static 
org.apache.flink.connector.dynamodb.source.enumerator.SplitAssignmentStatus.ASSIGNED;
@@ -58,6 +60,9 @@ public class SplitTracker {
     private final Map<String, Set<String>> parentChildSplitMap = new 
ConcurrentHashMap<>();
     private final Set<String> assignedSplits = new HashSet<>();
     private final Set<String> finishedSplits = new HashSet<>();
+    // Untracked ancestors already warned about, so the warning is logged once 
per shard rather
+    // than on every discovery while the shard is still listed.
+    private final Set<String> warnedSkippedAncestors = new HashSet<>();
     private final String streamArn;
     private final InitialPosition initialPosition;
     private final Instant startTimestamp;
@@ -95,15 +100,24 @@ public class SplitTracker {
      * @param shardsToAdd collection of splits to add to tracking
      */
     public void addSplits(Collection<Shard> shardsToAdd) {
-        if (TRIM_HORIZON.equals(initialPosition)) {
-            addSplitsForTrimHorizon(shardsToAdd);
+        if (AT_TIMESTAMP.equals(initialPosition)) {
+            addSplitsForAtTimestamp(shardsToAdd);
             return;
         }
-        addSplitsForLatest(shardsToAdd);
+        if (LATEST.equals(initialPosition)) {
+            addSplitsForLatest(shardsToAdd);
+            return;
+        }
+        addSplitsWithPosition(shardsToAdd, initialPosition);
     }
 
     public void addChildSplits(Collection<Shard> childShardsToAdd) {
-        addSplitsForTrimHorizon(childShardsToAdd);
+        // Under AT_TIMESTAMP, children from the finish-handoff also start at 
AT_TIMESTAMP, so
+        // they match shards found by periodic discovery and never emit 
pre-timestamp records.
+        // Other initial positions keep the TRIM_HORIZON handoff.
+        InitialPosition childPosition =
+                AT_TIMESTAMP.equals(initialPosition) ? AT_TIMESTAMP : 
TRIM_HORIZON;
+        addSplitsWithPosition(childShardsToAdd, childPosition);
     }
 
     private void addSplitsForLatest(Collection<Shard> shardsToAdd) {
@@ -159,15 +173,22 @@ public class SplitTracker {
         return null;
     }
 
-    private void addSplitsForTrimHorizon(Collection<Shard> shardsToAdd) {
+    /** Tracks each shard (if not already known) with the given initial 
position. */
+    private void addSplitsWithPosition(Collection<Shard> shardsToAdd, 
InitialPosition position) {
         for (Shard shard : shardsToAdd) {
-            String shardId = shard.shardId();
-            if (!knownSplits.containsKey(shardId)) {
-                DynamoDbStreamsShardSplit newSplit = mapToSplit(shard, 
TRIM_HORIZON);
-                knownSplits.put(shardId, newSplit);
-                addSplitToMapping(newSplit);
-            }
+            trackSplit(shard, position);
+        }
+    }
+
+    /** Adds a single shard to tracking with the given initial position, 
unless already tracked. */
+    private void trackSplit(Shard shard, InitialPosition position) {
+        String shardId = shard.shardId();
+        if (knownSplits.containsKey(shardId)) {
+            return;
         }
+        DynamoDbStreamsShardSplit newSplit = mapToSplit(shard, position);
+        knownSplits.put(shardId, newSplit);
+        addSplitToMapping(newSplit);
     }
 
     private void addSplitToMapping(DynamoDbStreamsShardSplit split) {
@@ -179,6 +200,65 @@ public class SplitTracker {
                 .add(split.splitId());
     }
 
+    /**
+     * Adds shards discovered under the AT_TIMESTAMP initial position. Each 
shard that is not
+     * already tracked is added at AT_TIMESTAMP: 
GetShardIterator(AT_TIMESTAMP) resolves the
+     * per-record position, so a shard that straddles the timestamp never 
emits pre-timestamp
+     * records, and the position does not depend on the discovery path or 
restore timing.
+     *
+     * <p>Ancestors of already-tracked shards are not added, to keep the 
parent-before-child order.
+     * Usually they have already been read. An ancestor that was never tracked 
(for example, one an
+     * earlier discovery did not return) is skipped too, so any of its records 
at or after the
+     * timestamp are not read; a warning is logged once per such shard.
+     */
+    private void addSplitsForAtTimestamp(Collection<Shard> shardsToAdd) {
+        Map<String, Shard> shardIdToShardMap =
+                shardsToAdd.stream().collect(Collectors.toMap(Shard::shardId, 
shard -> shard));
+
+        Set<String> ancestorsOfTrackedSplits = 
findAncestorsOfTrackedSplits(shardIdToShardMap);
+
+        List<Shard> eligibleShards = new ArrayList<>();
+        for (Shard shard : shardsToAdd) {
+            String shardId = shard.shardId();
+            if (!ancestorsOfTrackedSplits.contains(shardId)) {
+                eligibleShards.add(shard);
+            } else if (!knownSplits.containsKey(shardId) && 
warnedSkippedAncestors.add(shardId)) {
+                LOG.warn(
+                        "Skipping shard {} of stream {}: it was never tracked 
but is an ancestor of "
+                                + "a tracked shard, so it is not read to keep 
the parent-before-child "
+                                + "order. Records in it at or after {} are not 
read.",
+                        shardId,
+                        streamArn,
+                        startTimestamp);
+            }
+        }
+        addSplitsWithPosition(eligibleShards, AT_TIMESTAMP);
+    }
+
+    private Set<String> findAncestorsOfTrackedSplits(Map<String, Shard> 
shardIdToShardMap) {
+        Set<String> ancestorsOfTrackedSplits = new HashSet<>();
+        // knownSplits already contains finished splits (they are removed from 
both only on
+        // cleanup), so its key set covers every tracked shard.
+        Set<String> trackedShardIds = new HashSet<>(knownSplits.keySet());
+
+        for (String trackedShardId : trackedShardIds) {
+            Shard shard = shardIdToShardMap.get(trackedShardId);
+            if (shard == null) {
+                continue;
+            }
+            String currentParentId = shard.parentShardId();
+            while (currentParentId != null && 
!ancestorsOfTrackedSplits.contains(currentParentId)) {
+                ancestorsOfTrackedSplits.add(currentParentId);
+                Shard parentShard = shardIdToShardMap.get(currentParentId);
+                if (parentShard == null) {
+                    break;
+                }
+                currentParentId = parentShard.parentShardId();
+            }
+        }
+        return ancestorsOfTrackedSplits;
+    }
+
     private DynamoDbStreamsShardSplit mapToSplit(
             Shard shard, DynamodbStreamsSourceConfigConstants.InitialPosition 
initialPosition) {
         StartingPosition startingPosition;
@@ -186,6 +266,9 @@ public class SplitTracker {
             case LATEST:
                 startingPosition = StartingPosition.latest();
                 break;
+            case AT_TIMESTAMP:
+                startingPosition = 
StartingPosition.atTimestamp(startTimestamp);
+                break;
             case TRIM_HORIZON:
             default:
                 startingPosition = StartingPosition.fromStart();
diff --git 
a/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/proxy/DynamoDbStreamsProxy.java
 
b/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/proxy/DynamoDbStreamsProxy.java
index 133da8dd..0755460b 100644
--- 
a/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/proxy/DynamoDbStreamsProxy.java
+++ 
b/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/proxy/DynamoDbStreamsProxy.java
@@ -40,6 +40,7 @@ import 
software.amazon.awssdk.services.dynamodb.streams.DynamoDbStreamsClient;
 import javax.annotation.Nullable;
 
 import java.io.IOException;
+import java.time.Instant;
 import java.util.Collections;
 import java.util.Map;
 import java.util.Objects;
@@ -225,6 +226,16 @@ public class DynamoDbStreamsProxy implements StreamProxy {
             case TRIM_HORIZON:
             case LATEST:
                 break;
+            case AT_TIMESTAMP:
+                if (startingPosition.getStartingMarker() instanceof Instant) {
+                    requestBuilder =
+                            requestBuilder.timestamp(
+                                    (Instant) 
startingPosition.getStartingMarker());
+                } else {
+                    throw new IllegalArgumentException(
+                            "Invalid object given for 
GetShardIteratorRequest() when ShardIteratorType is AT_TIMESTAMP. Must be an 
Instant.");
+                }
+                break;
             case AT_SEQUENCE_NUMBER:
             case AFTER_SEQUENCE_NUMBER:
                 if (startingPosition.getStartingMarker() instanceof String) {
diff --git 
a/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/split/DynamoDbStreamsShardSplitSerializer.java
 
b/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/split/DynamoDbStreamsShardSplitSerializer.java
index 10ddc79d..4c9eb08c 100644
--- 
a/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/split/DynamoDbStreamsShardSplitSerializer.java
+++ 
b/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/split/DynamoDbStreamsShardSplitSerializer.java
@@ -31,6 +31,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.IOException;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -45,8 +46,9 @@ import java.util.Set;
 public class DynamoDbStreamsShardSplitSerializer
         implements SimpleVersionedSerializer<DynamoDbStreamsShardSplit> {
 
-    private static final Set<Integer> COMPATIBLE_VERSIONS = new 
HashSet<>(Arrays.asList(0, 1, 2));
-    private static final int CURRENT_VERSION = 2;
+    private static final Set<Integer> COMPATIBLE_VERSIONS =
+            new HashSet<>(Arrays.asList(0, 1, 2, 3));
+    private static final int CURRENT_VERSION = 3;
 
     @Override
     public int getVersion() {
@@ -69,6 +71,12 @@ public class DynamoDbStreamsShardSplitSerializer
                 out.writeBoolean(startingMarker instanceof String);
                 if (startingMarker instanceof String) {
                     out.writeUTF((String) startingMarker);
+                } else if (startingMarker instanceof Instant) {
+                    out.writeLong(((Instant) startingMarker).toEpochMilli());
+                } else {
+                    throw new IOException(
+                            "Unsupported starting marker type: "
+                                    + startingMarker.getClass().getName());
                 }
             }
             if (split.getParentShardId() == null) {
@@ -116,8 +124,11 @@ public class DynamoDbStreamsShardSplitSerializer
 
             final boolean hasStartingMarker = in.readBoolean();
             if (hasStartingMarker) {
-                if (in.readBoolean()) {
+                boolean isString = in.readBoolean();
+                if (isString) {
                     startingMarker = in.readUTF();
+                } else if (version >= 3) {
+                    startingMarker = Instant.ofEpochMilli(in.readLong());
                 }
             }
 
diff --git 
a/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/split/StartingPosition.java
 
b/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/split/StartingPosition.java
index 7de3cfaa..a9089737 100644
--- 
a/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/split/StartingPosition.java
+++ 
b/flink-connector-aws/flink-connector-dynamodb/src/main/java/org/apache/flink/connector/dynamodb/source/split/StartingPosition.java
@@ -24,6 +24,7 @@ import 
software.amazon.awssdk.services.dynamodb.model.ShardIteratorType;
 
 import javax.annotation.Nullable;
 
+import java.time.Instant;
 import java.util.Objects;
 
 import static 
software.amazon.awssdk.services.dynamodb.model.ShardIteratorType.AFTER_SEQUENCE_NUMBER;
@@ -63,6 +64,10 @@ public final class StartingPosition {
         return new StartingPosition(LATEST, null);
     }
 
+    public static StartingPosition atTimestamp(Instant timestamp) {
+        return new StartingPosition(ShardIteratorType.AT_TIMESTAMP, timestamp);
+    }
+
     @Override
     public String toString() {
         return "StartingPosition{"
diff --git 
a/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/config/DynamodbStreamsSourceConfigUtilTest.java
 
b/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/config/DynamodbStreamsSourceConfigUtilTest.java
new file mode 100644
index 00000000..b58a99c3
--- /dev/null
+++ 
b/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/config/DynamodbStreamsSourceConfigUtilTest.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 org.apache.flink.connector.dynamodb.source.config;
+
+import org.apache.flink.configuration.Configuration;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.time.Instant;
+
+import static 
org.apache.flink.connector.dynamodb.source.config.DynamodbStreamsSourceConfigConstants.STREAM_INITIAL_POSITION;
+import static 
org.apache.flink.connector.dynamodb.source.config.DynamodbStreamsSourceConfigConstants.STREAM_INITIAL_TIMESTAMP;
+import static 
org.apache.flink.connector.dynamodb.source.config.DynamodbStreamsSourceConfigConstants.STREAM_INITIAL_TIMESTAMP_FORMAT;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/** Tests for {@link DynamodbStreamsSourceConfigUtil}. */
+class DynamodbStreamsSourceConfigUtilTest {
+
+    @Test
+    void testParseInitialTimestampWithDefaultFormat() {
+        Configuration sourceConfig = new Configuration();
+        sourceConfig.set(STREAM_INITIAL_TIMESTAMP, "2024-05-01T12:00:00.000Z");
+
+        Instant parsed = 
DynamodbStreamsSourceConfigUtil.parseInitialTimestamp(sourceConfig);
+
+        assertThat(parsed).isEqualTo(Instant.parse("2024-05-01T12:00:00Z"));
+    }
+
+    @Test
+    void testParseInitialTimestampFallsBackToEpochSeconds() {
+        Configuration sourceConfig = new Configuration();
+        // Not in the configured date format -> interpreted as epoch seconds.
+        sourceConfig.set(STREAM_INITIAL_TIMESTAMP, "1704067200");
+
+        Instant parsed = 
DynamodbStreamsSourceConfigUtil.parseInitialTimestamp(sourceConfig);
+
+        assertThat(parsed).isEqualTo(Instant.ofEpochMilli(1704067200000L));
+    }
+
+    @Test
+    void testParseInitialTimestampWithCustomFormat() {
+        Configuration sourceConfig = new Configuration();
+        sourceConfig.set(STREAM_INITIAL_TIMESTAMP_FORMAT, "dd/MM/yyyy HH:mm:ss 
XXX");
+        sourceConfig.set(STREAM_INITIAL_TIMESTAMP, "01/05/2024 14:00:00 
+02:00");
+
+        Instant parsed = 
DynamodbStreamsSourceConfigUtil.parseInitialTimestamp(sourceConfig);
+
+        assertThat(parsed).isEqualTo(Instant.parse("2024-05-01T12:00:00Z"));
+    }
+
+    @Test
+    void testParseInitialTimestampThrowsWhenUnparseable() {
+        Configuration sourceConfig = new Configuration();
+        sourceConfig.set(STREAM_INITIAL_TIMESTAMP, "not-a-timestamp");
+
+        assertThatThrownBy(
+                        () -> 
DynamodbStreamsSourceConfigUtil.parseInitialTimestamp(sourceConfig))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Unable to parse")
+                .hasMessageContaining("not-a-timestamp");
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {"NaN", "Infinity", "-1"})
+    void 
testParseInitialTimestampRejectsNonFiniteOrNegativeEpochSeconds(String value) {
+        Configuration sourceConfig = new Configuration();
+        sourceConfig.set(STREAM_INITIAL_TIMESTAMP, value);
+
+        assertThatThrownBy(
+                        () -> 
DynamodbStreamsSourceConfigUtil.parseInitialTimestamp(sourceConfig))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("non-negative epoch-second value");
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {"2024-02-30T00:00:00.000Z", 
"2024-05-01T12:00:00.000Zgarbage"})
+    void 
testParseInitialTimestampRejectsInvalidOrPartiallyMatchingDates(String value) {
+        Configuration sourceConfig = new Configuration();
+        sourceConfig.set(STREAM_INITIAL_TIMESTAMP, value);
+
+        assertThatThrownBy(
+                        () -> 
DynamodbStreamsSourceConfigUtil.parseInitialTimestamp(sourceConfig))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Unable to parse");
+    }
+
+    @Test
+    void testValidateReturnsTheValidatedTimestamp() {
+        Configuration sourceConfig = new Configuration();
+        sourceConfig.set(
+                STREAM_INITIAL_POSITION,
+                
DynamodbStreamsSourceConfigConstants.InitialPosition.AT_TIMESTAMP);
+        sourceConfig.set(STREAM_INITIAL_TIMESTAMP, "2024-05-01T12:00:00.000Z");
+
+        
assertThat(DynamodbStreamsSourceConfigUtil.validateStreamSourceConfiguration(sourceConfig))
+                .isEqualTo(Instant.parse("2024-05-01T12:00:00Z"));
+    }
+
+    @Test
+    void testValidateReturnsNullForNonTimestampPositions() {
+        Configuration sourceConfig = new Configuration();
+        sourceConfig.set(
+                STREAM_INITIAL_POSITION,
+                DynamodbStreamsSourceConfigConstants.InitialPosition.LATEST);
+
+        
assertThat(DynamodbStreamsSourceConfigUtil.validateStreamSourceConfiguration(sourceConfig))
+                .isNull();
+    }
+
+    @Test
+    void testParseInitialTimestampThrowsWhenMissing() {
+        Configuration sourceConfig = new Configuration();
+
+        assertThatThrownBy(
+                        () -> 
DynamodbStreamsSourceConfigUtil.parseInitialTimestamp(sourceConfig))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining(STREAM_INITIAL_TIMESTAMP.key());
+    }
+
+    @Test
+    void testValidateFailsFastForAtTimestampWithoutTimestamp() {
+        Configuration sourceConfig = new Configuration();
+        sourceConfig.set(
+                STREAM_INITIAL_POSITION,
+                
DynamodbStreamsSourceConfigConstants.InitialPosition.AT_TIMESTAMP);
+
+        assertThatThrownBy(
+                        () ->
+                                
DynamodbStreamsSourceConfigUtil.validateStreamSourceConfiguration(
+                                        sourceConfig))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining(STREAM_INITIAL_TIMESTAMP.key());
+    }
+
+    @Test
+    void testValidatePassesForAtTimestampWithTimestamp() {
+        Configuration sourceConfig = new Configuration();
+        sourceConfig.set(
+                STREAM_INITIAL_POSITION,
+                
DynamodbStreamsSourceConfigConstants.InitialPosition.AT_TIMESTAMP);
+        sourceConfig.set(STREAM_INITIAL_TIMESTAMP, "2024-05-01T12:00:00.000Z");
+
+        assertThatNoException()
+                .isThrownBy(
+                        () ->
+                                
DynamodbStreamsSourceConfigUtil.validateStreamSourceConfiguration(
+                                        sourceConfig));
+    }
+
+    @Test
+    void testValidateRejectsFutureAtTimestamp() {
+        // A future AT_TIMESTAMP must fail fast at source construction, so the 
job halts instead of
+        // restarting on GetShardIterator rejections until the clock passes 
the timestamp.
+        Configuration sourceConfig = new Configuration();
+        sourceConfig.set(
+                STREAM_INITIAL_POSITION,
+                
DynamodbStreamsSourceConfigConstants.InitialPosition.AT_TIMESTAMP);
+        sourceConfig.set(
+                STREAM_INITIAL_TIMESTAMP,
+                
String.valueOf(Instant.now().plusSeconds(60).getEpochSecond()));
+
+        assertThatThrownBy(
+                        () ->
+                                
DynamodbStreamsSourceConfigUtil.validateStreamSourceConfiguration(
+                                        sourceConfig))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("future")
+                .hasMessageContaining(STREAM_INITIAL_TIMESTAMP.key());
+    }
+
+    @Test
+    void testValidatePassesForCurrentAtTimestamp() {
+        Configuration sourceConfig = new Configuration();
+        sourceConfig.set(
+                STREAM_INITIAL_POSITION,
+                
DynamodbStreamsSourceConfigConstants.InitialPosition.AT_TIMESTAMP);
+        sourceConfig.set(STREAM_INITIAL_TIMESTAMP, 
String.valueOf(Instant.now().getEpochSecond()));
+
+        assertThatNoException()
+                .isThrownBy(
+                        () ->
+                                
DynamodbStreamsSourceConfigUtil.validateStreamSourceConfiguration(
+                                        sourceConfig));
+    }
+
+    @Test
+    void testValidateIsNoOpForNonTimestampPositions() {
+        Configuration sourceConfig = new Configuration();
+        sourceConfig.set(
+                STREAM_INITIAL_POSITION,
+                
DynamodbStreamsSourceConfigConstants.InitialPosition.TRIM_HORIZON);
+
+        // No timestamp set, but validation must not fail for 
TRIM_HORIZON/LATEST.
+        assertThatNoException()
+                .isThrownBy(
+                        () ->
+                                
DynamodbStreamsSourceConfigUtil.validateStreamSourceConfiguration(
+                                        sourceConfig));
+    }
+}
diff --git 
a/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/enumerator/DynamoDbStreamsSourceEnumeratorTest.java
 
b/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/enumerator/DynamoDbStreamsSourceEnumeratorTest.java
index 4c543a3b..b255abda 100644
--- 
a/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/enumerator/DynamoDbStreamsSourceEnumeratorTest.java
+++ 
b/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/enumerator/DynamoDbStreamsSourceEnumeratorTest.java
@@ -44,11 +44,13 @@ import 
software.amazon.awssdk.services.dynamodb.model.ShardIteratorType;
 import java.time.Duration;
 import java.time.Instant;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Stream;
 
 import static 
org.apache.flink.connector.dynamodb.source.config.DynamodbStreamsSourceConfigConstants.STREAM_INITIAL_POSITION;
+import static 
org.apache.flink.connector.dynamodb.source.config.DynamodbStreamsSourceConfigConstants.STREAM_INITIAL_TIMESTAMP;
 import static 
org.apache.flink.connector.dynamodb.source.util.DynamoDbStreamsProxyProvider.getTestStreamProxy;
 import static 
org.apache.flink.connector.dynamodb.source.util.TestUtil.generateShard;
 import static 
org.apache.flink.connector.dynamodb.source.util.TestUtil.generateShardId;
@@ -84,6 +86,7 @@ class DynamoDbStreamsSourceEnumeratorTest {
                             context,
                             STREAM_ARN,
                             sourceConfig,
+                            null,
                             streamProxy,
                             ShardAssignerFactory.uniformShardAssigner(),
                             null);
@@ -140,6 +143,260 @@ class DynamoDbStreamsSourceEnumeratorTest {
         }
     }
 
+    @Test
+    void testStartWithAtTimestampListsAllShardsAndAssignsAtTimestamp() throws 
Throwable {
+        try (MockSplitEnumeratorContext<DynamoDbStreamsShardSplit> context =
+                new MockSplitEnumeratorContext<>(NUM_SUBTASKS)) {
+            DynamoDbStreamsProxyProvider.TestDynamoDbStreamsProxy streamProxy =
+                    getTestStreamProxy();
+            final Configuration sourceConfig = new Configuration();
+            sourceConfig.set(
+                    STREAM_INITIAL_POSITION,
+                    
DynamodbStreamsSourceConfigConstants.InitialPosition.AT_TIMESTAMP);
+            sourceConfig.set(STREAM_INITIAL_TIMESTAMP, 
"2024-01-01T00:00:00.000Z");
+
+            DynamoDbStreamsSourceEnumerator enumerator =
+                    new DynamoDbStreamsSourceEnumerator(
+                            context,
+                            STREAM_ARN,
+                            sourceConfig,
+                            Instant.parse("2024-01-01T00:00:00.000Z"),
+                            streamProxy,
+                            ShardAssignerFactory.uniformShardAssigner(),
+                            null);
+            enumerator.start();
+
+            final int subtaskId = 1;
+            context.registerReader(TestUtil.getTestReaderInfo(subtaskId));
+            enumerator.addReader(subtaskId);
+            Shard[] shards =
+                    new Shard[] {
+                        generateShard(0, "1200", null, null), generateShard(1, 
"1300", null, null)
+                    };
+            streamProxy.addShards(shards);
+
+            // Discovery lists all shards; every one is assigned at 
AT_TIMESTAMP.
+            context.runNextOneTimeCallable();
+
+            SplitsAssignment<DynamoDbStreamsShardSplit> initialSplitAssignment 
=
+                    context.getSplitsAssignmentSequence().get(0);
+            // Every discovered shard is assigned with the AT_TIMESTAMP 
starting position; the
+            // per-shard GetShardIterator(AT_TIMESTAMP) resolves the position 
at read time.
+            assertThat(
+                            
initialSplitAssignment.assignment().get(subtaskId).stream()
+                                    
.map(DynamoDbStreamsShardSplit::getStartingPosition))
+                    .allSatisfy(
+                            s ->
+                                    assertThat(s.getShardIteratorType())
+                                            
.isEqualTo(ShardIteratorType.AT_TIMESTAMP));
+        }
+    }
+
+    @Test
+    void testAtTimestampUsesTheTimestampResolvedBySource() throws Throwable {
+        try (MockSplitEnumeratorContext<DynamoDbStreamsShardSplit> context =
+                new MockSplitEnumeratorContext<>(NUM_SUBTASKS)) {
+            DynamoDbStreamsProxyProvider.TestDynamoDbStreamsProxy streamProxy =
+                    getTestStreamProxy();
+            final Configuration sourceConfig = new Configuration();
+            sourceConfig.set(
+                    STREAM_INITIAL_POSITION,
+                    
DynamodbStreamsSourceConfigConstants.InitialPosition.AT_TIMESTAMP);
+            // The enumerator must not re-parse this; it uses the instant the 
source resolved.
+            sourceConfig.set(STREAM_INITIAL_TIMESTAMP, 
"2024-01-01T00:00:00.000Z");
+            final Instant resolved = Instant.parse("2023-06-01T00:00:00Z");
+
+            DynamoDbStreamsSourceEnumerator enumerator =
+                    new DynamoDbStreamsSourceEnumerator(
+                            context,
+                            STREAM_ARN,
+                            sourceConfig,
+                            resolved,
+                            streamProxy,
+                            ShardAssignerFactory.uniformShardAssigner(),
+                            null);
+            enumerator.start();
+
+            final int subtaskId = 1;
+            context.registerReader(TestUtil.getTestReaderInfo(subtaskId));
+            enumerator.addReader(subtaskId);
+            streamProxy.addShards(generateShard(0, "1200", null, null));
+            context.runNextOneTimeCallable();
+
+            assertThat(
+                            context
+                                    .getSplitsAssignmentSequence()
+                                    .get(0)
+                                    .assignment()
+                                    .get(subtaskId)
+                                    .stream()
+                                    
.map(DynamoDbStreamsShardSplit::getStartingPosition))
+                    .containsExactly(StartingPosition.atTimestamp(resolved));
+            
assertThat(enumerator.snapshotState(1L).getStartTimestamp()).isEqualTo(resolved);
+        }
+    }
+
+    @Test
+    void testAtTimestampSecondDiscoveryAddsNoDuplicates() throws Throwable {
+        try (MockSplitEnumeratorContext<DynamoDbStreamsShardSplit> context =
+                new MockSplitEnumeratorContext<>(NUM_SUBTASKS)) {
+            DynamoDbStreamsProxyProvider.TestDynamoDbStreamsProxy streamProxy =
+                    getTestStreamProxy();
+            final Configuration sourceConfig = new Configuration();
+            sourceConfig.set(
+                    STREAM_INITIAL_POSITION,
+                    
DynamodbStreamsSourceConfigConstants.InitialPosition.AT_TIMESTAMP);
+            sourceConfig.set(STREAM_INITIAL_TIMESTAMP, 
"2024-01-01T00:00:00.000Z");
+            DynamoDbStreamsSourceEnumerator enumerator =
+                    new DynamoDbStreamsSourceEnumerator(
+                            context,
+                            STREAM_ARN,
+                            sourceConfig,
+                            Instant.parse("2024-01-01T00:00:00.000Z"),
+                            streamProxy,
+                            ShardAssignerFactory.uniformShardAssigner(),
+                            null);
+            enumerator.start();
+            final int subtaskId = 0;
+            context.registerReader(TestUtil.getTestReaderInfo(subtaskId));
+            enumerator.addReader(subtaskId);
+            streamProxy.addShards(
+                    generateShard(0, "1200", null, null), generateShard(1, 
"1300", null, null));
+
+            // Bootstrap assigns both shards.
+            context.runNextOneTimeCallable();
+            
assertThat(context.getSplitsAssignmentSequence().get(0).assignment().get(subtaskId))
+                    .hasSize(2);
+
+            // A periodic shard discovery over the SAME shard set adds/assigns 
nothing new: dedup
+            // keeps it idempotent even though the full listing returns every 
shard again.
+            context.runPeriodicCallable(0);
+            
assertThat(context.getSplitsAssignmentSequence().get(1).assignment().get(subtaskId))
+                    .isNullOrEmpty();
+        }
+    }
+
+    @Test
+    void testAtTimestampChildIsGatedBehindParentAndNotDuplicated() throws 
Throwable {
+        try (MockSplitEnumeratorContext<DynamoDbStreamsShardSplit> context =
+                new MockSplitEnumeratorContext<>(NUM_SUBTASKS)) {
+            DynamoDbStreamsProxyProvider.TestDynamoDbStreamsProxy streamProxy =
+                    getTestStreamProxy();
+            final Configuration sourceConfig = new Configuration();
+            sourceConfig.set(
+                    STREAM_INITIAL_POSITION,
+                    
DynamodbStreamsSourceConfigConstants.InitialPosition.AT_TIMESTAMP);
+            sourceConfig.set(STREAM_INITIAL_TIMESTAMP, 
"2024-01-01T00:00:00.000Z");
+            DynamoDbStreamsSourceEnumerator enumerator =
+                    new DynamoDbStreamsSourceEnumerator(
+                            context,
+                            STREAM_ARN,
+                            sourceConfig,
+                            Instant.parse("2024-01-01T00:00:00.000Z"),
+                            streamProxy,
+                            ShardAssignerFactory.uniformShardAssigner(),
+                            null);
+            enumerator.start();
+            final int subtaskId = 0;
+            context.registerReader(TestUtil.getTestReaderInfo(subtaskId));
+            enumerator.addReader(subtaskId);
+
+            // Parent (closed) and its child (open) are both present at 
bootstrap.
+            Shard parent = generateShard(1, "100", "200", null);
+            Shard child = generateShard(2, "300", null, parent.shardId());
+            streamProxy.addShards(parent, child);
+
+            // Bootstrap: only the parent is assigned; the child is gated 
behind it.
+            context.runNextOneTimeCallable();
+            SplitsAssignment<DynamoDbStreamsShardSplit> bootstrap =
+                    context.getSplitsAssignmentSequence().get(0);
+            assertThat(
+                            bootstrap.assignment().get(subtaskId).stream()
+                                    
.map(DynamoDbStreamsShardSplit::getShardId))
+                    .containsExactly(parent.shardId());
+            assertThat(
+                            bootstrap.assignment().get(subtaskId).stream()
+                                    
.map(DynamoDbStreamsShardSplit::getStartingPosition)
+                                    
.map(StartingPosition::getShardIteratorType))
+                    .containsExactly(ShardIteratorType.AT_TIMESTAMP);
+
+            // When the parent finishes, the child is assigned with the 
AT_TIMESTAMP position it
+            // was tracked with at bootstrap; the handoff does not add it 
again (dedup).
+            enumerator.handleSourceEvent(
+                    subtaskId,
+                    new SplitsFinishedEvent(
+                            Collections.singleton(
+                                    new SplitsFinishedEventContext(
+                                            parent.shardId(), 
Collections.singletonList(child)))));
+            SplitsAssignment<DynamoDbStreamsShardSplit> afterFinish =
+                    context.getSplitsAssignmentSequence().get(1);
+            assertThat(
+                            afterFinish.assignment().get(subtaskId).stream()
+                                    
.map(DynamoDbStreamsShardSplit::getShardId))
+                    .containsExactly(child.shardId());
+            assertThat(
+                            afterFinish.assignment().get(subtaskId).stream()
+                                    
.map(DynamoDbStreamsShardSplit::getStartingPosition)
+                                    
.map(StartingPosition::getShardIteratorType))
+                    .containsExactly(ShardIteratorType.AT_TIMESTAMP);
+        }
+    }
+
+    @Test
+    void testAtTimestampChildDiscoveredViaHandoffGetsAtTimestamp() throws 
Throwable {
+        try (MockSplitEnumeratorContext<DynamoDbStreamsShardSplit> context =
+                new MockSplitEnumeratorContext<>(NUM_SUBTASKS)) {
+            DynamoDbStreamsProxyProvider.TestDynamoDbStreamsProxy streamProxy =
+                    getTestStreamProxy();
+            final Configuration sourceConfig = new Configuration();
+            sourceConfig.set(
+                    STREAM_INITIAL_POSITION,
+                    
DynamodbStreamsSourceConfigConstants.InitialPosition.AT_TIMESTAMP);
+            sourceConfig.set(STREAM_INITIAL_TIMESTAMP, 
"2024-01-01T00:00:00.000Z");
+            DynamoDbStreamsSourceEnumerator enumerator =
+                    new DynamoDbStreamsSourceEnumerator(
+                            context,
+                            STREAM_ARN,
+                            sourceConfig,
+                            Instant.parse("2024-01-01T00:00:00.000Z"),
+                            streamProxy,
+                            ShardAssignerFactory.uniformShardAssigner(),
+                            null);
+            enumerator.start();
+            final int subtaskId = 0;
+            context.registerReader(TestUtil.getTestReaderInfo(subtaskId));
+            enumerator.addReader(subtaskId);
+
+            // Only the parent is present at bootstrap; the child appears 
later via the
+            // finish-handoff.
+            Shard parent = generateShard(1, "100", "200", null);
+            streamProxy.addShards(parent);
+            context.runNextOneTimeCallable();
+
+            Shard child = generateShard(2, "300", null, parent.shardId());
+            enumerator.handleSourceEvent(
+                    subtaskId,
+                    new SplitsFinishedEvent(
+                            Collections.singleton(
+                                    new SplitsFinishedEventContext(
+                                            parent.shardId(), 
Collections.singletonList(child)))));
+
+            // A child discovered only via the handoff still starts at 
AT_TIMESTAMP
+            // (not TRIM_HORIZON), so a straddling shard can never emit 
pre-timestamp records.
+            SplitsAssignment<DynamoDbStreamsShardSplit> afterFinish =
+                    context.getSplitsAssignmentSequence().get(1);
+            assertThat(
+                            afterFinish.assignment().get(subtaskId).stream()
+                                    
.map(DynamoDbStreamsShardSplit::getShardId))
+                    .containsExactly(child.shardId());
+            assertThat(
+                            afterFinish.assignment().get(subtaskId).stream()
+                                    
.map(DynamoDbStreamsShardSplit::getStartingPosition)
+                                    
.map(StartingPosition::getShardIteratorType))
+                    .containsExactly(ShardIteratorType.AT_TIMESTAMP);
+        }
+    }
+
     @Test
     void 
testLatestOnlyAssignsTheLeafNodesAndSkipsParentShardsDuringInitialDiscovery()
             throws Throwable {
@@ -158,6 +415,7 @@ class DynamoDbStreamsSourceEnumeratorTest {
                             context,
                             STREAM_ARN,
                             sourceConfig,
+                            null,
                             streamProxy,
                             ShardAssignerFactory.uniformShardAssigner(),
                             null);
@@ -229,6 +487,7 @@ class DynamoDbStreamsSourceEnumeratorTest {
                             context,
                             STREAM_ARN,
                             sourceConfig,
+                            null,
                             streamProxy,
                             ShardAssignerFactory.uniformShardAssigner(),
                             null);
@@ -369,6 +628,7 @@ class DynamoDbStreamsSourceEnumeratorTest {
                             context,
                             STREAM_ARN,
                             sourceConfig,
+                            null,
                             streamProxy,
                             ShardAssignerFactory.uniformShardAssigner(),
                             state);
@@ -472,6 +732,7 @@ class DynamoDbStreamsSourceEnumeratorTest {
                             context,
                             STREAM_ARN,
                             sourceConfig,
+                            null,
                             streamProxy,
                             ShardAssignerFactory.uniformShardAssigner(),
                             null);
@@ -502,6 +763,7 @@ class DynamoDbStreamsSourceEnumeratorTest {
                             context,
                             STREAM_ARN,
                             sourceConfig,
+                            null,
                             streamProxy,
                             ShardAssignerFactory.uniformShardAssigner(),
                             null);
@@ -560,6 +822,7 @@ class DynamoDbStreamsSourceEnumeratorTest {
                             context,
                             STREAM_ARN,
                             sourceConfig,
+                            null,
                             streamProxy,
                             ShardAssignerFactory.uniformShardAssigner(),
                             null);
@@ -626,6 +889,7 @@ class DynamoDbStreamsSourceEnumeratorTest {
                             context,
                             STREAM_ARN,
                             sourceConfig,
+                            null,
                             streamProxy,
                             ShardAssignerFactory.uniformShardAssigner(),
                             null);
@@ -700,6 +964,7 @@ class DynamoDbStreamsSourceEnumeratorTest {
                             context,
                             STREAM_ARN,
                             sourceConfig,
+                            null,
                             streamProxy,
                             ShardAssignerFactory.uniformShardAssigner(),
                             null);
@@ -751,6 +1016,7 @@ class DynamoDbStreamsSourceEnumeratorTest {
                             context,
                             STREAM_ARN,
                             sourceConfig,
+                            null,
                             streamProxy,
                             ShardAssignerFactory.uniformShardAssigner(),
                             null);
@@ -805,6 +1071,7 @@ class DynamoDbStreamsSourceEnumeratorTest {
                             context,
                             STREAM_ARN,
                             sourceConfig,
+                            null,
                             streamProxy,
                             ShardAssignerFactory.uniformShardAssigner(),
                             null);
@@ -826,6 +1093,7 @@ class DynamoDbStreamsSourceEnumeratorTest {
                             context,
                             STREAM_ARN,
                             sourceConfig,
+                            null,
                             streamProxy,
                             ShardAssignerFactory.uniformShardAssigner(),
                             null);
@@ -845,6 +1113,7 @@ class DynamoDbStreamsSourceEnumeratorTest {
                         context,
                         STREAM_ARN,
                         sourceConfig,
+                        null,
                         streamProxy,
                         ShardAssignerFactory.uniformShardAssigner(),
                         null);
diff --git 
a/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/enumerator/tracker/SplitTrackerTest.java
 
b/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/enumerator/tracker/SplitTrackerTest.java
index d2dc8289..7a9e00b4 100644
--- 
a/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/enumerator/tracker/SplitTrackerTest.java
+++ 
b/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/enumerator/tracker/SplitTrackerTest.java
@@ -23,8 +23,11 @@ import 
org.apache.flink.connector.dynamodb.source.enumerator.DynamoDBStreamsShar
 import 
org.apache.flink.connector.dynamodb.source.enumerator.SplitAssignmentStatus;
 import 
org.apache.flink.connector.dynamodb.source.split.DynamoDbStreamsShardSplit;
 import org.apache.flink.connector.dynamodb.source.split.StartingPosition;
+import org.apache.flink.testutils.logging.LoggerAuditingExtension;
 
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.slf4j.event.Level;
 import software.amazon.awssdk.services.dynamodb.model.Shard;
 
 import java.time.Duration;
@@ -47,6 +50,10 @@ import static 
org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
 
 class SplitTrackerTest {
 
+    @RegisterExtension
+    final LoggerAuditingExtension splitTrackerLogs =
+            new LoggerAuditingExtension(SplitTracker.class, Level.WARN);
+
     @Test
     public void testSplitsWithoutParentsAreAvailableToAssign() {
         Instant startTime = Instant.now();
@@ -539,4 +546,213 @@ class SplitTrackerTest {
         return new DynamoDbStreamsShardSplit(
                 STREAM_ARN, shard.shardId(), StartingPosition.fromStart(), 
shard.parentShardId());
     }
+
+    @Test
+    public void testAtTimestampBootstrapAddsAllShardsWithTimestampPosition() {
+        Instant startTime = Instant.now();
+        SplitTracker splitTracker =
+                new SplitTracker(STREAM_ARN, InitialPosition.AT_TIMESTAMP, 
startTime);
+
+        List<Shard> shards =
+                Arrays.asList(
+                        generateShard(5, "5000", null, null), generateShard(6, 
"6000", null, null));
+
+        splitTracker.addSplits(shards);
+
+        assertThat(splitTracker.getKnownSplits()).hasSize(2);
+        splitTracker
+                .getKnownSplits()
+                .values()
+                .forEach(
+                        split ->
+                                assertThat(split.getStartingPosition())
+                                        
.isEqualTo(StartingPosition.atTimestamp(startTime)));
+    }
+
+    @Test
+    public void testAtTimestampRootShardGetsAtTimestamp() {
+        Instant startTime = Instant.now();
+        SplitTracker splitTracker =
+                new SplitTracker(STREAM_ARN, InitialPosition.AT_TIMESTAMP, 
startTime);
+
+        // A shard with no tracked parent starts at AT_TIMESTAMP.
+        splitTracker.addSplits(Collections.singletonList(generateShard(0, 
"100", null, null)));
+
+        
assertThat(splitTracker.getKnownSplits().get(generateShardId(0)).getStartingPosition())
+                .isEqualTo(StartingPosition.atTimestamp(startTime));
+    }
+
+    @Test
+    public void testAtTimestampChildOfPreExistingParentGetsAtTimestamp() {
+        Instant startTime = Instant.now();
+        SplitTracker splitTracker =
+                new SplitTracker(STREAM_ARN, InitialPosition.AT_TIMESTAMP, 
startTime);
+
+        // Cycle 1: parent (root) is tracked.
+        splitTracker.addSplits(Collections.singletonList(generateShard(0, 
"100", null, null)));
+
+        // Cycle 2: parent has closed and a child appears. The child starts at
+        // AT_TIMESTAMP (not TRIM_HORIZON) regardless of the parent being 
tracked before this cycle.
+        splitTracker.addSplits(
+                Arrays.asList(
+                        generateShard(0, "100", "200", null),
+                        generateShard(1, "200", null, generateShardId(0))));
+
+        
assertThat(splitTracker.getKnownSplits().get(generateShardId(1)).getStartingPosition())
+                .isEqualTo(StartingPosition.atTimestamp(startTime));
+    }
+
+    @Test
+    public void 
testAtTimestampParentAndChildAddedInSameCycleBothStartAtTimestamp() {
+        Instant startTime = Instant.now();
+        SplitTracker splitTracker =
+                new SplitTracker(STREAM_ARN, InitialPosition.AT_TIMESTAMP, 
startTime);
+
+        // Cycle 1: grandparent (root) is tracked.
+        splitTracker.addSplits(Collections.singletonList(generateShard(0, 
"100", null, null)));
+
+        // Cycle 2: grandparent(0) closed; parent(1) and child(2) both appear 
new in this cycle.
+        splitTracker.addSplits(
+                Arrays.asList(
+                        generateShard(0, "100", "200", null),
+                        generateShard(1, "200", "300", generateShardId(0)),
+                        generateShard(2, "300", null, generateShardId(1))));
+
+        // parent(1): every shard starts at AT_TIMESTAMP.
+        
assertThat(splitTracker.getKnownSplits().get(generateShardId(1)).getStartingPosition())
+                .isEqualTo(StartingPosition.atTimestamp(startTime));
+        // child(2): also AT_TIMESTAMP, though its parent(1) was added in the 
same cycle.
+        
assertThat(splitTracker.getKnownSplits().get(generateShardId(2)).getStartingPosition())
+                .isEqualTo(StartingPosition.atTimestamp(startTime));
+    }
+
+    @Test
+    public void testAtTimestampPeriodicDiscoverySkipsAncestors() {
+        Instant startTime = Instant.now();
+        SplitTracker splitTracker =
+                new SplitTracker(STREAM_ARN, InitialPosition.AT_TIMESTAMP, 
startTime);
+
+        // Bootstrap: only shards 5 and 6 (active at T)
+        List<Shard> bootstrapShards =
+                Arrays.asList(
+                        generateShard(5, "5000", null, generateShardId(3)),
+                        generateShard(6, "6000", null, generateShardId(3)));
+        splitTracker.addSplits(bootstrapShards);
+
+        assertThat(splitTracker.getKnownSplits()).hasSize(2);
+
+        // Periodic discovery: returns all shards including ancestors
+        List<Shard> allShards =
+                Arrays.asList(
+                        generateShard(0, "100", "500", null), // root
+                        generateShard(1, "500", "1000", generateShardId(0)), 
// child of 0
+                        generateShard(3, "1000", "3000", generateShardId(1)), 
// parent of 5,6
+                        generateShard(5, "5000", null, generateShardId(3)),
+                        generateShard(6, "6000", null, generateShardId(3)));
+        splitTracker.addSplits(allShards);
+
+        // Ancestors 0, 1, 3 should be skipped
+        assertThat(splitTracker.getKnownSplits()).hasSize(2);
+        
assertThat(splitTracker.getKnownSplits().containsKey(generateShardId(0))).isFalse();
+        
assertThat(splitTracker.getKnownSplits().containsKey(generateShardId(1))).isFalse();
+        
assertThat(splitTracker.getKnownSplits().containsKey(generateShardId(3))).isFalse();
+    }
+
+    @Test
+    public void testAtTimestampWarnsOnceForSkippedUntrackedAncestor() {
+        Instant startTime = Instant.now();
+        SplitTracker splitTracker =
+                new SplitTracker(STREAM_ARN, InitialPosition.AT_TIMESTAMP, 
startTime);
+
+        // Only the child is tracked at bootstrap; its parent (shard 0) is 
never tracked.
+        splitTracker.addSplits(
+                Collections.singletonList(generateShard(1, "200", null, 
generateShardId(0))));
+
+        List<Shard> allShards =
+                Arrays.asList(
+                        generateShard(0, "100", "200", null),
+                        generateShard(1, "200", null, generateShardId(0)));
+        // Several periodic discoveries keep listing the skipped parent.
+        splitTracker.addSplits(allShards);
+        splitTracker.addSplits(allShards);
+        splitTracker.addSplits(allShards);
+
+        
assertThat(splitTracker.getKnownSplits()).doesNotContainKey(generateShardId(0));
+        assertThat(splitTrackerLogs.getMessages())
+                .filteredOn(message -> message.contains(generateShardId(0)))
+                .hasSize(1);
+    }
+
+    @Test
+    public void testAtTimestampMissedShardAddedByPeriodicDiscovery() {
+        Instant startTime = Instant.now();
+        SplitTracker splitTracker =
+                new SplitTracker(STREAM_ARN, InitialPosition.AT_TIMESTAMP, 
startTime);
+
+        // Bootstrap: shard 5 and 6 returned, shard 7 missed
+        //     Shard 0
+        //    /       \
+        // Shard 1   Shard 2
+        //   |          |
+        // Shard 3   Shard 7 (missed)
+        //  / \
+        // 5   6
+        List<Shard> bootstrapShards =
+                Arrays.asList(
+                        generateShard(5, "5000", null, generateShardId(3)),
+                        generateShard(6, "6000", null, generateShardId(3)));
+        splitTracker.addSplits(bootstrapShards);
+
+        // Periodic discovery returns all including missed shard 7
+        List<Shard> allShards =
+                Arrays.asList(
+                        generateShard(0, "100", "500", null),
+                        generateShard(1, "500", "1000", generateShardId(0)),
+                        generateShard(2, "500", "4000", generateShardId(0)),
+                        generateShard(3, "1000", "3000", generateShardId(1)),
+                        generateShard(5, "5000", null, generateShardId(3)),
+                        generateShard(6, "6000", null, generateShardId(3)),
+                        generateShard(7, "7000", null, generateShardId(2)));
+        splitTracker.addSplits(allShards);
+
+        // Shard 7 should be added at AT_TIMESTAMP (missed, not an ancestor of 
anything tracked).
+        
assertThat(splitTracker.getKnownSplits().containsKey(generateShardId(7))).isTrue();
+        
assertThat(splitTracker.getKnownSplits().get(generateShardId(7)).getStartingPosition())
+                .isEqualTo(StartingPosition.atTimestamp(startTime));
+
+        // Shard 2 should also be added (parent of missed shard 7). Its parent 
(shard 0) is neither
+        // tracked before this cycle nor added this cycle, so shard 2 starts 
at AT_TIMESTAMP.
+        
assertThat(splitTracker.getKnownSplits().containsKey(generateShardId(2))).isTrue();
+        
assertThat(splitTracker.getKnownSplits().get(generateShardId(2)).getStartingPosition())
+                .isEqualTo(StartingPosition.atTimestamp(startTime));
+
+        // Ancestors of tracked shards (0, 1, 3) should be skipped
+        
assertThat(splitTracker.getKnownSplits().containsKey(generateShardId(0))).isFalse();
+        
assertThat(splitTracker.getKnownSplits().containsKey(generateShardId(1))).isFalse();
+        
assertThat(splitTracker.getKnownSplits().containsKey(generateShardId(3))).isFalse();
+    }
+
+    @Test
+    public void testAtTimestampChildSplitsGetTimestampPosition() {
+        Instant startTime = Instant.now();
+        SplitTracker splitTracker =
+                new SplitTracker(STREAM_ARN, InitialPosition.AT_TIMESTAMP, 
startTime);
+
+        List<Shard> bootstrapShards =
+                Collections.singletonList(generateShard(5, "5000", null, 
null));
+        splitTracker.addSplits(bootstrapShards);
+
+        // Child shards discovered via the finish-handoff. They start at
+        // AT_TIMESTAMP so a straddling child never emits pre-timestamp 
records.
+        List<Shard> childShards =
+                Arrays.asList(
+                        generateShard(8, "8000", null, generateShardId(5)),
+                        generateShard(9, "9000", null, generateShardId(5)));
+        splitTracker.addChildSplits(childShards);
+
+        
assertThat(splitTracker.getKnownSplits().get(generateShardId(8)).getStartingPosition())
+                .isEqualTo(StartingPosition.atTimestamp(startTime));
+        
assertThat(splitTracker.getKnownSplits().get(generateShardId(9)).getStartingPosition())
+                .isEqualTo(StartingPosition.atTimestamp(startTime));
+    }
 }
diff --git 
a/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/proxy/DynamoDbStreamsProxyTest.java
 
b/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/proxy/DynamoDbStreamsProxyTest.java
index 9c662a77..c5306064 100644
--- 
a/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/proxy/DynamoDbStreamsProxyTest.java
+++ 
b/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/proxy/DynamoDbStreamsProxyTest.java
@@ -45,6 +45,7 @@ import 
software.amazon.awssdk.services.dynamodb.model.StreamDescription;
 import software.amazon.awssdk.services.dynamodb.model.StreamStatus;
 import 
software.amazon.awssdk.services.dynamodb.model.TrimmedDataAccessException;
 
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -442,6 +443,44 @@ public class DynamoDbStreamsProxyTest {
         return shards;
     }
 
+    @Test
+    void testGetRecordsInitialReadFromAtTimestamp() {
+        final String streamArn =
+                
"arn:aws:dynamodb:us-east-1:1231231230:table/test/stream/2024-01-01T00:00:00.826";
+        final String shardId = "shardId-000000000002";
+        final Instant atTimestamp = Instant.ofEpochMilli(1704067200000L);
+        final StartingPosition startingPosition = 
StartingPosition.atTimestamp(atTimestamp);
+
+        final String expectedShardIterator = "some-shard-iterator";
+        final GetRecordsResponse expectedGetRecordsResponse =
+                GetRecordsResponse.builder()
+                        .records(Record.builder().build())
+                        .nextShardIterator("next-iterator")
+                        .build();
+
+        TestingDynamoDbStreamsClient testingDynamoDbStreamsClient =
+                new TestingDynamoDbStreamsClient();
+        
testingDynamoDbStreamsClient.setNextShardIterator(expectedShardIterator);
+        testingDynamoDbStreamsClient.setShardIteratorValidation(
+                validateEqual(
+                        GetShardIteratorRequest.builder()
+                                .streamArn(streamArn)
+                                .shardId(shardId)
+                                
.shardIteratorType(ShardIteratorType.AT_TIMESTAMP)
+                                .timestamp(atTimestamp)
+                                .build()));
+        
testingDynamoDbStreamsClient.setGetRecordsResponse(expectedGetRecordsResponse);
+        testingDynamoDbStreamsClient.setGetRecordsValidation(
+                validateEqual(
+                        
GetRecordsRequest.builder().shardIterator(expectedShardIterator).build()));
+
+        DynamoDbStreamsProxy dynamoDbStreamsProxy =
+                new DynamoDbStreamsProxy(testingDynamoDbStreamsClient, 
HTTP_CLIENT);
+
+        assertThat(dynamoDbStreamsProxy.getRecords(streamArn, shardId, 
startingPosition))
+                .isEqualTo(expectedGetRecordsResponse);
+    }
+
     private Consumer<DescribeStreamRequest> getDescribeStreamRequestValidation(
             final String streamArn, final String startShardId) {
         return req -> {
diff --git 
a/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/split/DynamoDbStreamsShardSplitSerializerTest.java
 
b/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/split/DynamoDbStreamsShardSplitSerializerTest.java
index 410f93bc..ce4630e8 100644
--- 
a/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/split/DynamoDbStreamsShardSplitSerializerTest.java
+++ 
b/flink-connector-aws/flink-connector-dynamodb/src/test/java/org/apache/flink/connector/dynamodb/source/split/DynamoDbStreamsShardSplitSerializerTest.java
@@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 
+import java.time.Instant;
 import java.util.stream.Stream;
 
 import static 
org.apache.flink.connector.dynamodb.source.util.TestUtil.SHARD_ID;
@@ -76,11 +77,38 @@ class DynamoDbStreamsShardSplitSerializerTest {
         
assertThat(deserializedSplit).usingRecursiveComparison().isEqualTo(initialSplit);
     }
 
+    @Test
+    void testSerializeAtTimestampUsesVersion3AndRoundTrips() throws Exception {
+        final DynamoDbStreamsShardSplit initialSplit =
+                
getTestSplit(StartingPosition.atTimestamp(Instant.ofEpochMilli(1704067200000L)));
+        DynamoDbStreamsShardSplitSerializer serializer = new 
DynamoDbStreamsShardSplitSerializer();
+
+        assertThat(serializer.getVersion()).isEqualTo(3);
+        byte[] serialized = serializer.serialize(initialSplit);
+        DynamoDbStreamsShardSplit deserialized =
+                serializer.deserialize(serializer.getVersion(), serialized);
+        
assertThat(deserialized).usingRecursiveComparison().isEqualTo(initialSplit);
+    }
+
+    @Test
+    void testBackwardCompatibilityVersion2SplitDeserializesUnderVersion3() 
throws Exception {
+        // A split without an Instant marker is written identically under 
schema v2 and v3, so the
+        // v3 deserializer must read data tagged as version 2 (an older 
checkpoint) correctly.
+        final DynamoDbStreamsShardSplit initialSplit =
+                
getTestSplit(StartingPosition.continueFromSequenceNumber("some-sequence-number"));
+        DynamoDbStreamsShardSplitSerializer serializer = new 
DynamoDbStreamsShardSplitSerializer();
+
+        byte[] serialized = serializer.serialize(initialSplit);
+        DynamoDbStreamsShardSplit deserialized = serializer.deserialize(2, 
serialized);
+        
assertThat(deserialized).usingRecursiveComparison().isEqualTo(initialSplit);
+    }
+
     private static Stream<StartingPosition> provideStartingPositions() {
         return Stream.of(
                 StartingPosition.fromStart(),
                 
StartingPosition.continueFromSequenceNumber("some-sequence-number"),
-                StartingPosition.latest());
+                StartingPosition.latest(),
+                
StartingPosition.atTimestamp(Instant.ofEpochMilli(1704067200000L)));
     }
 
     @Test
diff --git 
a/flink-connector-aws/flink-connector-sqs/src/test/java/org/apache/flink/connector/sqs/sink/SqsSinkWriterTest.java
 
b/flink-connector-aws/flink-connector-sqs/src/test/java/org/apache/flink/connector/sqs/sink/SqsSinkWriterTest.java
index c8d7073e..8ea60f97 100644
--- 
a/flink-connector-aws/flink-connector-sqs/src/test/java/org/apache/flink/connector/sqs/sink/SqsSinkWriterTest.java
+++ 
b/flink-connector-aws/flink-connector-sqs/src/test/java/org/apache/flink/connector/sqs/sink/SqsSinkWriterTest.java
@@ -223,7 +223,7 @@ public class SqsSinkWriterTest {
             final boolean failOnError, TestSqsAsyncClientProvider 
testSqsAsyncClientProvider)
             throws IOException {
         TestSinkInitContext sinkInitContext = new TestSinkInitContext();
-        Properties sinkProperties = 
AWSServicesTestUtils.createConfig("https://fake_aws_endpoint";);
+        Properties sinkProperties = 
AWSServicesTestUtils.createConfig("https://fake-aws-endpoint";);
         SqsSink<String> sink =
                 new SqsSink<>(
                         ELEMENT_CONVERTER_PLACEHOLDER,
diff --git 
a/flink-connector-aws/flink-sql-connector-aws-kinesis-firehose/pom.xml 
b/flink-connector-aws/flink-sql-connector-aws-kinesis-firehose/pom.xml
index b18f0934..694a5131 100644
--- a/flink-connector-aws/flink-sql-connector-aws-kinesis-firehose/pom.xml
+++ b/flink-connector-aws/flink-sql-connector-aws-kinesis-firehose/pom.xml
@@ -68,6 +68,9 @@
                                     <include>com.typesafe.netty:*</include>
                                     
<include>commons-logging:commons-logging</include>
                                 </includes>
+                                <excludes>
+                                    
<exclude>software.amazon.awssdk:apache5-client</exclude>
+                                </excludes>
                             </artifactSet>
                             <relocations>
                                 <relocation>
diff --git 
a/flink-connector-aws/flink-sql-connector-aws-kinesis-firehose/src/main/resources/META-INF/NOTICE
 
b/flink-connector-aws/flink-sql-connector-aws-kinesis-firehose/src/main/resources/META-INF/NOTICE
index f9ae9dd2..270ce6d6 100644
--- 
a/flink-connector-aws/flink-sql-connector-aws-kinesis-firehose/src/main/resources/META-INF/NOTICE
+++ 
b/flink-connector-aws/flink-sql-connector-aws-kinesis-firehose/src/main/resources/META-INF/NOTICE
@@ -7,34 +7,36 @@ The Apache Software Foundation (http://www.apache.org/).
 
 This project bundles the following dependencies under the Apache Software 
License 2.0. (http://www.apache.org/licenses/LICENSE-2.0.txt)
 
-- software.amazon.awssdk:checksums-spi:2.40.3
-- software.amazon.awssdk:checksums:2.40.3
-- software.amazon.awssdk:utils:2.40.3
-- software.amazon.awssdk:third-party-jackson-core:2.40.3
-- software.amazon.awssdk:sts:2.40.3
-- software.amazon.awssdk:sdk-core:2.40.3
-- software.amazon.awssdk:regions:2.40.3
-- software.amazon.awssdk:retries-spi:2.40.3
-- software.amazon.awssdk:retries:2.40.3
-- software.amazon.awssdk:protocol-core:2.40.3
-- software.amazon.awssdk:profiles:2.40.3
-- software.amazon.awssdk:netty-nio-client:2.40.3
-- software.amazon.awssdk:metrics-spi:2.40.3
-- software.amazon.awssdk:json-utils:2.40.3
-- software.amazon.awssdk:http-client-spi:2.40.3
-- software.amazon.awssdk:http-auth-aws:2.40.3
-- software.amazon.awssdk:http-auth-spi:2.40.3
-- software.amazon.awssdk:http-auth:2.40.3
-- software.amazon.awssdk:identity-spi:2.40.3
-- software.amazon.awssdk:firehose:2.40.3
-- software.amazon.awssdk:endpoints-spi:2.40.3
-- software.amazon.awssdk:aws-query-protocol:2.40.3
-- software.amazon.awssdk:aws-json-protocol:2.40.3
-- software.amazon.awssdk:aws-core:2.40.3
-- software.amazon.awssdk:auth:2.40.3
-- software.amazon.awssdk:apache-client:2.40.3
-- software.amazon.awssdk:annotations:2.40.3
-- software.amazon.awssdk:arns:2.40.3
+- software.amazon.awssdk:checksums-spi:2.56.0
+- software.amazon.awssdk:checksums:2.56.0
+- software.amazon.awssdk:utils:2.56.0
+- software.amazon.awssdk:third-party-jackson-core:2.56.0
+- software.amazon.awssdk:sts:2.56.0
+- software.amazon.awssdk:sdk-core:2.56.0
+- software.amazon.awssdk:regions:2.56.0
+- software.amazon.awssdk:retries-spi:2.56.0
+- software.amazon.awssdk:retries:2.56.0
+- software.amazon.awssdk:protocol-core:2.56.0
+- software.amazon.awssdk:profiles:2.56.0
+- software.amazon.awssdk:netty-nio-client:2.56.0
+- software.amazon.awssdk:metrics-spi:2.56.0
+- software.amazon.awssdk:json-utils:2.56.0
+- software.amazon.awssdk:http-client-spi:2.56.0
+- software.amazon.awssdk:http-auth-aws:2.56.0
+- software.amazon.awssdk:http-auth-spi:2.56.0
+- software.amazon.awssdk:http-auth:2.56.0
+- software.amazon.awssdk:identity-spi:2.56.0
+- software.amazon.awssdk:firehose:2.56.0
+- software.amazon.awssdk:endpoints-spi:2.56.0
+- software.amazon.awssdk:aws-query-protocol:2.56.0
+- software.amazon.awssdk:aws-json-protocol:2.56.0
+- software.amazon.awssdk:aws-core:2.56.0
+- software.amazon.awssdk:auth:2.56.0
+- software.amazon.awssdk:apache-client:2.56.0
+- software.amazon.awssdk:annotations:2.56.0
+- software.amazon.awssdk:arns:2.56.0
+- software.amazon.awssdk:http-auth-aws-eventstream:2.56.0
+- software.amazon.awssdk:utils-lite:2.56.0
 - org.apache.httpcomponents:httpcore:4.4.14
 - org.apache.httpcomponents:httpclient:4.5.13
 - io.netty:netty-transport:4.1.86.Final
diff --git 
a/flink-connector-aws/flink-sql-connector-aws-kinesis-streams/pom.xml 
b/flink-connector-aws/flink-sql-connector-aws-kinesis-streams/pom.xml
index 29b66050..e86169c5 100644
--- a/flink-connector-aws/flink-sql-connector-aws-kinesis-streams/pom.xml
+++ b/flink-connector-aws/flink-sql-connector-aws-kinesis-streams/pom.xml
@@ -68,6 +68,9 @@
                                     <include>io.netty:*</include>
                                     
<include>commons-logging:commons-logging</include>
                                 </includes>
+                                <excludes>
+                                    
<exclude>software.amazon.awssdk:apache5-client</exclude>
+                                </excludes>
                             </artifactSet>
                             <relocations>
                                 <relocation>
diff --git 
a/flink-connector-aws/flink-sql-connector-aws-kinesis-streams/src/main/resources/META-INF/NOTICE
 
b/flink-connector-aws/flink-sql-connector-aws-kinesis-streams/src/main/resources/META-INF/NOTICE
index 36a9fad0..d2437fcb 100644
--- 
a/flink-connector-aws/flink-sql-connector-aws-kinesis-streams/src/main/resources/META-INF/NOTICE
+++ 
b/flink-connector-aws/flink-sql-connector-aws-kinesis-streams/src/main/resources/META-INF/NOTICE
@@ -7,36 +7,38 @@ The Apache Software Foundation (http://www.apache.org/).
 
 This project bundles the following dependencies under the Apache Software 
License 2.0. (http://www.apache.org/licenses/LICENSE-2.0.txt)
 
-- software.amazon.awssdk:checksums-spi:2.40.3
-- software.amazon.awssdk:checksums:2.40.3
-- software.amazon.awssdk:utils:2.40.3
-- software.amazon.awssdk:third-party-jackson-dataformat-cbor:2.40.3
-- software.amazon.awssdk:third-party-jackson-core:2.40.3
-- software.amazon.awssdk:sts:2.40.3
-- software.amazon.awssdk:sdk-core:2.40.3
-- software.amazon.awssdk:regions:2.40.3
-- software.amazon.awssdk:retries-spi:2.40.3
-- software.amazon.awssdk:retries:2.40.3
-- software.amazon.awssdk:protocol-core:2.40.3
-- software.amazon.awssdk:profiles:2.40.3
-- software.amazon.awssdk:netty-nio-client:2.40.3
-- software.amazon.awssdk:metrics-spi:2.40.3
-- software.amazon.awssdk:kinesis:2.40.3
-- software.amazon.awssdk:json-utils:2.40.3
-- software.amazon.awssdk:http-client-spi:2.40.3
-- software.amazon.awssdk:http-auth-aws:2.40.3
-- software.amazon.awssdk:http-auth-spi:2.40.3
-- software.amazon.awssdk:http-auth:2.40.3
-- software.amazon.awssdk:identity-spi:2.40.3
-- software.amazon.awssdk:endpoints-spi:2.40.3
-- software.amazon.awssdk:aws-query-protocol:2.40.3
-- software.amazon.awssdk:aws-json-protocol:2.40.3
-- software.amazon.awssdk:aws-core:2.40.3
-- software.amazon.awssdk:aws-cbor-protocol:2.40.3
-- software.amazon.awssdk:auth:2.40.3
-- software.amazon.awssdk:arns:2.40.3
-- software.amazon.awssdk:apache-client:2.40.3
-- software.amazon.awssdk:annotations:2.40.3
+- software.amazon.awssdk:checksums-spi:2.56.0
+- software.amazon.awssdk:checksums:2.56.0
+- software.amazon.awssdk:utils:2.56.0
+- software.amazon.awssdk:third-party-jackson-dataformat-cbor:2.56.0
+- software.amazon.awssdk:third-party-jackson-core:2.56.0
+- software.amazon.awssdk:sts:2.56.0
+- software.amazon.awssdk:sdk-core:2.56.0
+- software.amazon.awssdk:regions:2.56.0
+- software.amazon.awssdk:retries-spi:2.56.0
+- software.amazon.awssdk:retries:2.56.0
+- software.amazon.awssdk:protocol-core:2.56.0
+- software.amazon.awssdk:profiles:2.56.0
+- software.amazon.awssdk:netty-nio-client:2.56.0
+- software.amazon.awssdk:metrics-spi:2.56.0
+- software.amazon.awssdk:kinesis:2.56.0
+- software.amazon.awssdk:json-utils:2.56.0
+- software.amazon.awssdk:http-client-spi:2.56.0
+- software.amazon.awssdk:http-auth-aws:2.56.0
+- software.amazon.awssdk:http-auth-spi:2.56.0
+- software.amazon.awssdk:http-auth:2.56.0
+- software.amazon.awssdk:identity-spi:2.56.0
+- software.amazon.awssdk:endpoints-spi:2.56.0
+- software.amazon.awssdk:aws-query-protocol:2.56.0
+- software.amazon.awssdk:aws-json-protocol:2.56.0
+- software.amazon.awssdk:aws-core:2.56.0
+- software.amazon.awssdk:aws-cbor-protocol:2.56.0
+- software.amazon.awssdk:auth:2.56.0
+- software.amazon.awssdk:arns:2.56.0
+- software.amazon.awssdk:apache-client:2.56.0
+- software.amazon.awssdk:annotations:2.56.0
+- software.amazon.awssdk:http-auth-aws-eventstream:2.56.0
+- software.amazon.awssdk:utils-lite:2.56.0
 - org.apache.httpcomponents:httpcore:4.4.14
 - org.apache.httpcomponents:httpclient:4.5.13
 - io.netty:netty-transport:4.1.86.Final
diff --git a/flink-connector-aws/flink-sql-connector-dynamodb/pom.xml 
b/flink-connector-aws/flink-sql-connector-dynamodb/pom.xml
index f14b2e2a..93e8cd65 100644
--- a/flink-connector-aws/flink-sql-connector-dynamodb/pom.xml
+++ b/flink-connector-aws/flink-sql-connector-dynamodb/pom.xml
@@ -78,6 +78,9 @@ under the License.
                                     
<include>commons-logging:commons-logging</include>
                                     
<include>commons-codec:commons-codec</include>
                                 </includes>
+                                <excludes>
+                                    
<exclude>software.amazon.awssdk:apache5-client</exclude>
+                                </excludes>
                             </artifactSet>
                             <relocations>
                                 <relocation>
diff --git 
a/flink-connector-aws/flink-sql-connector-dynamodb/src/main/resources/META-INF/NOTICE
 
b/flink-connector-aws/flink-sql-connector-dynamodb/src/main/resources/META-INF/NOTICE
index 558a8784..aa443902 100644
--- 
a/flink-connector-aws/flink-sql-connector-dynamodb/src/main/resources/META-INF/NOTICE
+++ 
b/flink-connector-aws/flink-sql-connector-dynamodb/src/main/resources/META-INF/NOTICE
@@ -7,35 +7,37 @@ The Apache Software Foundation (http://www.apache.org/).
 
 This project bundles the following dependencies under the Apache Software 
License 2.0. (http://www.apache.org/licenses/LICENSE-2.0.txt)
 
-- software.amazon.awssdk:checksums-spi:2.40.3
-- software.amazon.awssdk:checksums:2.40.3
-- software.amazon.awssdk:utils:2.40.3
-- software.amazon.awssdk:third-party-jackson-core:2.40.3
-- software.amazon.awssdk:sts:2.40.3
-- software.amazon.awssdk:sdk-core:2.40.3
-- software.amazon.awssdk:regions:2.40.3
-- software.amazon.awssdk:retries-spi:2.40.3
-- software.amazon.awssdk:retries:2.40.3
-- software.amazon.awssdk:protocol-core:2.40.3
-- software.amazon.awssdk:profiles:2.40.3
-- software.amazon.awssdk:netty-nio-client:2.40.3
-- software.amazon.awssdk:metrics-spi:2.40.3
-- software.amazon.awssdk:json-utils:2.40.3
-- software.amazon.awssdk:http-client-spi:2.40.3
-- software.amazon.awssdk:http-auth-aws:2.40.3
-- software.amazon.awssdk:http-auth-spi:2.40.3
-- software.amazon.awssdk:http-auth:2.40.3
-- software.amazon.awssdk:identity-spi:2.40.3
-- software.amazon.awssdk:endpoints-spi:2.40.3
-- software.amazon.awssdk:dynamodb:2.40.3
-- software.amazon.awssdk:dynamodb-enhanced:2.40.3
-- software.amazon.awssdk:aws-query-protocol:2.40.3
-- software.amazon.awssdk:aws-json-protocol:2.40.3
-- software.amazon.awssdk:aws-core:2.40.3
-- software.amazon.awssdk:auth:2.40.3
-- software.amazon.awssdk:arns:2.40.3
-- software.amazon.awssdk:apache-client:2.40.3
-- software.amazon.awssdk:annotations:2.40.3
+- software.amazon.awssdk:checksums-spi:2.56.0
+- software.amazon.awssdk:checksums:2.56.0
+- software.amazon.awssdk:utils:2.56.0
+- software.amazon.awssdk:third-party-jackson-core:2.56.0
+- software.amazon.awssdk:sts:2.56.0
+- software.amazon.awssdk:sdk-core:2.56.0
+- software.amazon.awssdk:regions:2.56.0
+- software.amazon.awssdk:retries-spi:2.56.0
+- software.amazon.awssdk:retries:2.56.0
+- software.amazon.awssdk:protocol-core:2.56.0
+- software.amazon.awssdk:profiles:2.56.0
+- software.amazon.awssdk:netty-nio-client:2.56.0
+- software.amazon.awssdk:metrics-spi:2.56.0
+- software.amazon.awssdk:json-utils:2.56.0
+- software.amazon.awssdk:http-client-spi:2.56.0
+- software.amazon.awssdk:http-auth-aws:2.56.0
+- software.amazon.awssdk:http-auth-spi:2.56.0
+- software.amazon.awssdk:http-auth:2.56.0
+- software.amazon.awssdk:identity-spi:2.56.0
+- software.amazon.awssdk:endpoints-spi:2.56.0
+- software.amazon.awssdk:dynamodb:2.56.0
+- software.amazon.awssdk:dynamodb-enhanced:2.56.0
+- software.amazon.awssdk:aws-query-protocol:2.56.0
+- software.amazon.awssdk:aws-json-protocol:2.56.0
+- software.amazon.awssdk:aws-core:2.56.0
+- software.amazon.awssdk:auth:2.56.0
+- software.amazon.awssdk:arns:2.56.0
+- software.amazon.awssdk:apache-client:2.56.0
+- software.amazon.awssdk:annotations:2.56.0
+- software.amazon.awssdk:http-auth-aws-eventstream:2.56.0
+- software.amazon.awssdk:utils-lite:2.56.0
 - org.apache.httpcomponents:httpcore:4.4.14
 - org.apache.httpcomponents:httpclient:4.5.13
 - io.netty:netty-transport:4.1.86.Final
diff --git a/pom.xml b/pom.xml
index 098c169e..9a8d5158 100644
--- a/pom.xml
+++ b/pom.xml
@@ -53,7 +53,7 @@ under the License.
     </scm>
 
     <properties>
-        <aws.sdkv2.version>2.40.3</aws.sdkv2.version>
+        <aws.sdkv2.version>2.56.0</aws.sdkv2.version>
         <netty.version>4.1.86.Final</netty.version>
         <flink.version>2.1.2</flink.version>
         <jackson-bom.version>2.14.3</jackson-bom.version>

Reply via email to