dynamodb mocking reports cannot create preexisting table if there are more than 100 tables.
How did you install the Amplify CLI?
npm
If applicable, what version of Node.js are you using?
22
Amplify CLI Version
13, 14 and others
What operating system are you using?
Mac OS
Did you make any manual changes to the cloud resources managed by Amplify? Please describe the changes made.
nothing
Describe the bug
- define more than 100 tables in schema graphql file like
type Table000 @model {
id: String
}
type Table001 @model {
id: String
}
...
type Table101 @model {
id: String
}
- start mock server with command
amplify mock api - shut down mock server after server up with tables created.
- start mock server again with command
amplify mock api. it will fails with error
Creating new table XXXTable
Failed to start API Mocking. Running cleanup tasks.
Reason: Cannot create preexisting table
Expected behavior
it should not create new tables because it's already created.
Reproduction steps
- define more than 100tables in schema graphql file.
- start mock server with command
amplify mock api - shut down mock server after tables created.
- start mock server again. it will fails with error
Reason: Cannot create preexisting table
Project Identifier
No response
Log output
# Put your logs below this line
Creating new table XXTable
Failed to start API Mocking. Running cleanup tasks.
Reason: Cannot create preexisting table
Additional information
The cause is that DynamoDB Java Client's listTables method is limited to return 100 tables by default, the Local Dynamodb doesnt implement to list all tables that cause it trying to create exiting tables at mock server starting.
Before submitting, please confirm:
- [x] I have done my best to include a minimal, self-contained set of instructions for consistently reproducing the issue.
- [x] I have removed any sensitive information from my code snippets and submission.
Current vs Expected Behavior
Current Behavior:
┌─────────────────────────────────────────────────────────────┐
│ First Start: 101 Tables Defined in Schema │
└─────────────────────────────────────────────────────────────┘
|
v
┌───────────────────────┐
│ listTables() call │
└───────────────────────┘
|
v
┌───────────────────────┐
│ Returns: 0 tables │
│ (empty database) │
└───────────────────────┘
|
v
┌───────────────────────┐
│ Create all 101 tables │
└───────────────────────┘
|
v
✓ SUCCESS ✓
┌─────────────────────────────────────────────────────────────┐
│ Second Start: 101 Tables Already Exist │
└─────────────────────────────────────────────────────────────┘
|
v
┌───────────────────────┐
│ listTables() call │
└───────────────────────┘
|
v
┌───────────────────────┐
│ Returns: ONLY 100 │ ← BUG: Missing pagination
│ tables (limit reached)│
└───────────────────────┘
|
v
┌───────────────────────┐
│ Table #101 not found │
│ in returned list │
└───────────────────────┘
|
v
┌───────────────────────┐
│ Attempt to create │
│ Table #101 │
└───────────────────────┘
|
v
┌───────────────────────┐
│ DynamoDB Local Error: │
│ "Cannot create │
│ preexisting table" │
└───────────────────────┘
|
v
✗ FAILURE ✗
Expected Behavior:
┌─────────────────────────────────────────────────────────────┐
│ Second Start: 101 Tables Already Exist │
└─────────────────────────────────────────────────────────────┘
|
v
┌───────────────────────┐
│ listTables() call │
│ with pagination │
└───────────────────────┘
|
v
┌───────────────────────┐
│ Page 1: Tables 1-100 │
└───────────────────────┘
|
v
┌───────────────────────┐
│ LastEvaluatedTableName│
│ exists? YES │
└───────────────────────┘
|
v
┌───────────────────────┐
│ Page 2: Table 101 │
└───────────────────────┘
|
v
┌───────────────────────┐
│ All 101 tables found │
│ in complete list │
└───────────────────────┘
|
v
┌───────────────────────┐
│ Skip table creation │
│ (all exist) │
└───────────────────────┘
|
v
✓ SUCCESS ✓
Root Cause Analysis
The bug appears to be in packages/amplify-util-mock/src/utils/dynamo-db/index.ts:
export async function createAndUpdateTable(dynamoDbClient: DynamoDB, config: MockDynamoDBConfig): Promise<MockDynamoDBConfig> {
const tables = config.tables.map((table) => table.Properties);
const existingTables = await dynamoDbClient.listTables().promise(); // ← BUG HERE
const existingTablesWithDetails = await describeTables(dynamoDbClient, existingTables.TableNames);
const tablesToCreate = tables.filter((t) => {
const tableName = t.TableName;
return !existingTables.TableNames.includes(tableName); // ← Incomplete list
});
// ...
}
The Problem:
The AWS SDK's listTables() method returns a maximum of 100 table names per call. According to AWS DynamoDB API documentation, the response includes: • TableNames: Array of table names (max 100) • LastEvaluatedTableName: Pagination token if more tables exist
The code doesn't handle pagination, so: • Tables 1-100: Correctly detected as existing • Tables 101+: Not in the list, incorrectly treated as non-existent • Attempt to create table 101 fails because it already exists
Why it works on first run:
On the first run, no tables exist yet, so listTables() returns an empty array. All 101 tables are created successfully. The error only appears on subsequent restarts.
Document Evidence
• AWS SDK DynamoDB.listTables() documentation - Documents the 100-item limit and pagination • DynamoDB ListTables API Reference - Explains LastEvaluatedTableName pagination • Source code: index.ts - The problematic code
Possible Fixes/Solutions
The fix requires implementing pagination in the createAndUpdateTable function. The solution would involve:
- Replace single listTables() call with paginated loop that continues until LastEvaluatedTableName is undefined
- Accumulate all table names across pagination calls
- Use complete list for existence checking
The implementation pattern should follow AWS SDK pagination best practices, similar to how it's done elsewhere in the codebase (e.g., in DynamoDBService.ts which already handles pagination correctly).
Workaround for users:
Until fixed, users can manually delete the local DynamoDB data directory before each restart, though this loses all mock data.
Making this a bug. I'll bring this up to the team for review.