Fix DynamoDB ValidationException: "Provided Key Element Does Not Match the Schema" — Causes & Solutions
If DynamoDB is throwing ValidationException: The provided key element does not match the schema, the fix is almost never "the ID is wrong." Check three things in order: the exact attribute name (case matters), the exact attribute data type you're sending (S, N, or B), and whether you've supplied every part of the key — partition key alone, or partition key plus sort key. Here's the part that trips up experienced engineers: DynamoDB compares the type wrapper around your value, not just the value itself, so a partition key of "1042" can be the "correct" ID and still fail every single time if the table expects a Number and your code sent a String.
Jake called me on a Tuesday afternoon, which is never a good sign, because Jake only calls when something in his shop's repair-tracking app has stopped working in front of a customer. "I click 'look up repair,' I type in the ticket number I can literally see printed on the receipt in my hand, and it tells me the key doesn't match the schema. The ticket number is right there. I'm holding it." He'd already tried retyping it three times, slower each time, like the API was going to suddenly understand him better if he typed more carefully.
That instinct — "let me type it more carefully" — is the single most common wasted hour in this error. It's rarely a typo in the value. It's almost always a mismatch in the shape of what you're sending: the wrong attribute name, the wrong data type, or a key that's missing a piece the table actually requires. None of those show up by squinting at the screen. They show up by looking at the table's schema and comparing it, field by field, against exactly what your code is sending — not what you assume your code is sending.
What "the provided key element does not match the schema" actually means
DynamoDB is a NoSQL database, meaning it doesn't enforce a rigid table structure the way a spreadsheet or a traditional SQL database does — most attributes on an item can vary freely from row to row, and two items in the same table can even have completely different sets of non-key attributes. The one place DynamoDB is strict, and always strict, no exceptions, is the key schema: the small set of attributes (one or two of them) that it uses to find an item instantly instead of scanning the whole table. When you call GetItem, PutItem, UpdateItem, or DeleteItem, the Key object you send has to match that schema exactly — same attribute name, same attribute count, same data type. If any one of those three things is off, DynamoDB rejects the request before it even looks for the item. It returns HTTP status code 400 with the error name ValidationException and the message "The provided key element does not match the schema."
This is a request-shape problem, not a data problem. The item you're looking for might exist perfectly well in the table — the request just never gets far enough to check. That distinction matters because it changes where you should be looking. You're not debugging "why isn't this record there." You're debugging "why doesn't the request I'm sending look like what the table expects," which is a much narrower, much more mechanical question, and one with a finite list of possible answers.
🙋♂️ Jake's Reality Check
"So it's not even trying to find my repair ticket? It's just... refusing to ask?"
Yes, exactly. DynamoDB validates the shape of the key before it does anything else. If the shape is wrong, it never touches the table's data — the item could be sitting right there and you'd get the identical error.
A composite primary key — one with a partition key and a sort key — needs both parts present in every Key object, no exceptions. A simple primary key (partition key only) needs exactly that one attribute and nothing extra. Send too few key attributes, too many, the wrong names, or the wrong data types, and the request fails at the door, before DynamoDB has spent a single read or write unit trying to locate anything.
Step one: find out what your table's key schema actually is
Before touching a line of code, get DynamoDB to tell you, in its own words, what it thinks the key schema is. Do not trust the table definition in your infrastructure code, your teammate's memory, or a design doc from eight months ago. Ask the live table — it's the only source that can't be stale, because it's the thing enforcing the rule.
- Run
aws dynamodb describe-table --table-name YourTableNameusing the AWS CLI, or open the table in the DynamoDB console and click the Overview tab. - In the output, find
KeySchema. It lists one or two entries, each with anAttributeNameand aKeyType—HASHmeans partition key,RANGEmeans sort key. - Find
AttributeDefinitionsnext to it. This is where the data type lives —"S"for String,"N"for Number,"B"for Binary. Match each attribute name fromKeySchemaagainst its type here. - Write both attribute names down exactly as shown, capital letters and all, and note whether you have one key attribute or two.
Now compare that against the exact request your code is sending. Not the request you meant to send — the one that actually leaves your machine. If you're using an SDK, log the fully built parameters object right before the call goes out. A five-minute console.log() or print() statement here saves the hour Jake spent retyping a ticket number that was never the problem. Do this before you start changing code, because a fix applied against a guessed-at schema often "works" by accident, on one item, and then fails again the next time someone uses a differently-shaped ID.
🕐 What changed between versions
- Before: AWS SDK v2's
DocumentClientand today's@aws-sdk/lib-dynamodbboth hide the low-levelAttributeValuewrapper ({"S":"..."}) from you and marshal plain JavaScript values automatically. - Now: the raw
@aws-sdk/client-dynamodbpackage (and every other language's low-level client) still expects you to build that wrapper by hand — mixing the two styles in one call is one of the most common ways this error shows up after a library upgrade. - What that means: know which client you're actually instantiating, because "it worked before" often means "I was using the document client before and I'm not now."
Cause 1: the data type is wrong, even though the value looks right
This is the counterintuitive one, and it's the single most common cause of this error according to AWS's own troubleshooting guidance. Every value you send DynamoDB through the low-level API is wrapped in a small object that names its type: a string is {"S":"test-item01"}, a number is {"N":"2024"}, binary data is {"B":"..."}. If your table's AttributeDefinitions says a key attribute is type N and your code sends it wrapped as S — even with the exact right digits inside — DynamoDB treats that as a schema mismatch, not a "close enough, let me coerce it" situation. It does not attempt to convert types for you, and there's no setting anywhere that turns automatic coercion on.
| You sent | Table expects | Result |
|---|---|---|
{"Year":{"S":"2024"}} |
Number (N) | ValidationException — key element mismatch |
{"Year":{"N":"2024"}} |
Number (N) | Succeeds |
This is easy to trigger without noticing, because in most application code the value never announces its type. A ticket number pulled from a URL path parameter arrives as a string, always — Express, API Gateway, Flask, all of them hand you strings from the URL by default, no matter how numeric-looking the value is. If your table's partition key is numeric, you have to explicitly convert that string to a number (or, for the low-level API, wrap it as N) before it goes anywhere near DynamoDB. Skip that one line of conversion and the request fails every single time, on every single ID, with no exceptions — which is actually a useful diagnostic signal in itself: a type mismatch fails consistently, on every ID you try, not just on oddball ones. If your team files a bug that says "the lookup is completely broken, not one item works," that phrasing alone should point you here before anywhere else.
✅ Why this is the one to check first
Data type mismatches fail on 100% of requests, immediately, which makes them the fastest cause to rule in or out. If your error happens on every ID you try — not just some — start here before looking anywhere else.
Cause 2: the attribute name is misspelled, or the wrong case
DynamoDB attribute names are case-sensitive. Every name — table name, attribute name, index name — is required to be encoded as UTF-8, and DynamoDB treats userId, UserId, and userID as three completely different attributes. If your table's partition key attribute is literally named UserId and your application code builds a key object with userId, DynamoDB has no attribute by that name in its key schema, and the request is rejected as a mismatch — not a "not found," a mismatch, because from DynamoDB's point of view you haven't supplied a valid key at all, just an object it doesn't recognize the shape of.
This one is sneaky in teams where the table was created by one person (often via infrastructure-as-code, where a naming convention like camelCase vs. PascalCase gets typed once, months ago, and forgotten) and consumed by application code written by someone else, going from memory or from an outdated README rather than from the live schema. It's also sneaky when you rename an attribute in your application's model class but never touch the DynamoDB calls that reference the old name as a raw string — the compiler, if your language has one, generally has no way to know that the string "userId" is supposed to correspond to a field you just renamed to customerId.
⚠️ What this actually breaks
A misnamed key attribute isn't a typo you can eyeball reliably in a code review — userId and userld (lowercase L instead of capital I) look nearly identical in most fonts. Copy the attribute name directly out of the describe-table output and paste it into your code rather than retyping it from memory.
Attribute names in DynamoDB also carry their own length rules worth knowing when you're hunting for a mismatch: names must be at least one character and, for most attributes, less than 64 KB — but secondary index partition and sort key names, along with any user-specified projected attribute names on a local secondary index, are capped at 255 characters. It's rare to hit that ceiling by accident, but it's worth ruling out if you're working with a table generated programmatically or migrated from another system with long, auto-generated field names.
Cause 3: you left out part of a composite key
If your table has a composite primary key — a partition key plus a sort key, like CustomerId and OrderDate — both parts are required on every GetItem, PutItem, UpdateItem, and DeleteItem call. There's no such thing as "just look up the customer" with those operations when the table needs both. Sending only the partition key on a table that also has a sort key produces this exact error, and it's an extremely common one for anyone migrating an app from a simpler table design to one that now tracks history or multiple records per entity — a table that started life as "one row per customer" and grew into "one row per customer per order" without every code path getting updated to match.
The reverse mistake happens too: a table with only a partition key (a simple primary key, no sort key) gets a request that includes a second, extra attribute the schema never asked for. DynamoDB rejects that as well — an unnecessary additional key is its own listed cause of this error, right alongside missing ones. It's worth internalizing that DynamoDB's key requirement is exact, not "at least" — supplying more than the schema calls for is just as invalid as supplying less.
Cause 4: an extra attribute snuck into the Key object
This one usually comes from a copy-paste mistake, not a design misunderstanding. Someone builds a full item object — every attribute the record has, status field, timestamps, nested data and all — for a PutItem call, and then reuses that same object, unedited, as the Key parameter for a later GetItem or DeleteItem call, because it was sitting right there in scope and it felt wasteful to build a second, smaller object. The Key parameter is only ever supposed to contain the key attributes and nothing else — not the item's other fields, not a leftover debug property, not an extra ID your code adds for internal tracking. DynamoDB reads that extra attribute as part of the key you're claiming to look up, and since it isn't part of the actual schema, the whole request is rejected.
Ethan's rule of thumb here is blunt, and Jake didn't love hearing it the first time: "If you're not 100% sure what's in the object you're passing as Key, you don't actually know what your code is doing — you're guessing, and DynamoDB is the thing catching the guess." Building the key object explicitly, attribute by attribute, rather than slicing it out of a bigger object, removes this failure mode entirely, and it also makes the eventual code review faster, because a reviewer can see the whole key at a glance instead of tracing it back through three function calls to figure out what actually ends up on the wire.
Cause 5: you're pointed at the wrong table, or a secondary index that isn't a key at all
Every environment — dev, staging, production — usually gets its own DynamoDB table, often with a suffix on the name (Orders-dev, Orders-prod) or a completely different name generated by your infrastructure tooling. If your application's config points at the wrong environment's table, or a table name got hardcoded somewhere it shouldn't have, you can end up sending a perfectly correctly-shaped key to a table whose schema is completely different. The error looks identical to every other cause on this page, but the fix is a one-line config correction, not a code change — which is exactly why it's worth checking early rather than last, since it's the fastest one to rule out and the easiest one to waste an afternoon debugging as if it were a code problem.
A related trap: GetItem, PutItem, UpdateItem, and DeleteItem all operate on the base table's key schema — they cannot target a Global Secondary Index (GSI) or Local Secondary Index (LSI) directly. If you've been querying by an index's key attribute and try to reuse that same key shape for a direct GetItem call, it will fail unless that index's key attributes happen to also be the base table's primary key. Index lookups that aren't the table's primary key have to go through Query, specifying IndexName, not through GetItem.
Cause 6: your SDK's marshalling is fighting your table's actual types
Most language SDKs offer two tiers of client. The low-level client (DynamoDB in most SDKs) makes you build every value as an explicit AttributeValue object — {"S":"..."}, {"N":"..."}. The higher-level "document client" (DynamoDBDocumentClient / DocumentClient in JavaScript, resource-level clients in Python's boto3, enhanced clients in Java) lets you pass plain native values — strings, numbers, booleans — and handles the marshalling into AttributeValue form for you automatically, then unmarshals responses back into native types on the way out.
The bug that produces this error most often after a library upgrade: mixing the two. Building a key object by hand with explicit {"S": ...} wrappers and then passing it to a document client that's expecting a plain native value — or the reverse, passing a plain JavaScript number straight into the low-level client, which is expecting the wrapped form. Since AWS SDK for JavaScript v3 split the single DocumentClient class from v2 into a separate @aws-sdk/lib-dynamodb package that wraps @aws-sdk/client-dynamodb, teams migrating between v2 and v3 frequently end up with half-migrated code that imports the low-level client but still writes document-client-style key objects, or vice versa — often because the migration was done file by file over several sprints, and nobody went back to check that every call site actually changed its key-building style to match its new import.
| Client type | Key example | Common mistake |
|---|---|---|
Low-level (client-dynamodb) |
{"id":{"S":"abc"}} |
Passing a plain string instead of the wrapped object |
Document client (lib-dynamodb) |
{"id":"abc"} |
Passing a manually wrapped {"S":...} object, which the document client then tries to marshal again |
The fix is to pick one client style per code path and be consistent, and to check which one you're actually instantiating rather than assuming from the import statement — DynamoDBClient and DynamoDBDocumentClient from the same SDK behave differently on this exact point, and it's a one-word difference easy to miss in a code review, especially in a large file where the client was constructed at the top and the key-building code you're actually reviewing is fifty lines further down.
Cause 7: you're using GetItem to fetch more than one item
GetItem retrieves exactly one item by its complete primary key. It's not built to accept a partial key and return every item that matches — that's what Query is for. If your table has a composite key and you only know the partition key value, wanting "every order for this customer" rather than one specific order, calling GetItem with just the partition key isn't a smaller version of that request — it's an invalid one, because GetItem requires the complete key. Reach for Query instead: it lets you specify only the partition key (via KeyConditionExpression) and returns every matching item, sorted by the sort key, with the option to narrow further by a condition on that sort key if you know a date range or a prefix you're looking for.
If you don't even know the partition key, and want to search by some other attribute entirely — a customer's email address on a table keyed by customer ID, say — neither GetItem nor Query can help. That's what Scan with a FilterExpression is for, though it's worth knowing upfront that Scan reads every item in the table to apply that filter, which gets expensive fast on a large table and should generally be a last resort rather than a first instinct, best reserved for one-off administrative lookups rather than anything on a hot request path a customer is waiting on.
🙋♂️ Jake's Reality Check
"So GetItem, Query, and Scan aren't three flavors of the same thing? I thought they were basically interchangeable."
They're not. GetItem wants one complete key and gives you one item. Query wants a partition key (and optionally narrows by sort key) and gives you a set of items. Scan doesn't need a key at all, but it pays for that flexibility by reading the entire table.
BatchGetItem and BatchWriteItem: the same rules, with a nastier failure mode
Everything above applies to BatchGetItem and BatchWriteItem too, with one extra complication: these operations work across possibly many tables and many keys at once, and a schema mismatch on one key in the batch is treated the same as a mismatch on a single-item request — a ValidationException for the whole call. It doesn't silently skip the bad key and process the rest. This is a common surprise for teams who assume batch APIs are more forgiving; they aren't, on schema shape.
What batch operations do handle more gracefully is a different kind of partial failure — throttling. If some items in a large BatchWriteItem call can't be processed because your table's provisioned or on-demand capacity is temporarily exceeded, those specific items come back in an UnprocessedItems map for you to retry, without failing the whole batch. That's a capacity problem, not a schema problem, and it's worth not confusing the two error patterns: a ValidationException means your request shape is wrong for every affected item; unprocessed items in a successful response mean the shape was fine but DynamoDB couldn't get to them yet. If your batch job is failing entirely with a validation error, don't spend time adding retry-with-backoff logic for it — that solves throttling, and it will do nothing for a schema mismatch, which retries the same wrong shape forever.
Global and Local Secondary Indexes have their own key schemas
A table can have secondary indexes layered on top of its base primary key — a Global Secondary Index (GSI) with its own partition and sort key, or a Local Secondary Index (LSI), which shares the base table's partition key but defines a different sort key. Each of these has its own KeySchemaElement entries, separate from the base table's, and a KeySchemaElement must be a scalar top-level attribute — String, Number, or Binary — never a nested attribute inside a list or map.
The mismatch shows up in a specific, slightly different way for indexes: you can't call GetItem against a GSI at all — direct item lookups only work against the base table's primary key. To read through a GSI you use Query with IndexName set, and the KeyConditionExpression you write has to reference that index's own key attributes, not the base table's. Writing a Query against a GSI using the base table's partition key name, when the index actually uses a different attribute as its partition key, produces the same family of validation failure — it's still a key-schema mismatch, just scoped to the index instead of the table, and the error message won't always spell out which of the two you were actually trying to hit, so double-check the IndexName parameter and the attribute names you're referencing in the same request.
When infrastructure-as-code and application code quietly disagree
DynamoDB tables are commonly defined in CloudFormation, the AWS Cloud Development Kit (CDK), Terraform, or the Serverless Application Model — and the key schema is one of the very few table properties that can't simply be changed on an existing table. Changing a key attribute's name or type in your infrastructure template usually forces a table replacement (a new table gets created and the old one deleted) rather than an in-place update, precisely because the key schema is foundational to how DynamoDB physically stores and partitions data.
This creates a specific trap for teams working across a pull request: someone updates the infrastructure template to rename a key attribute, someone else's application code is still on the old branch referencing the old name, and the two get deployed out of sync — application code hits a freshly-replaced table whose key schema no longer matches what the code expects. The fix isn't really a code fix at all; it's a process fix — treat key schema changes as breaking changes that need coordinated deploys, the same way you'd treat a breaking API contract change, not as a routine schema tweak that can ride along in whichever pull request happens to merge first.
If you're running DynamoDB Local for development or CI testing, the same principle applies with an added twist: a stale local Docker image or an old table definition file that never got updated when the "real" table schema changed will produce this exact error locally while production behaves fine, or the reverse. Nobody has run a comparison to prove local and production stay in sync automatically — they don't, unless your tooling explicitly keeps the table definitions as a single source of truth for both, typically by generating the local table-creation script from the same infrastructure template that deploys the real one, rather than maintaining two definitions by hand.
Serverless and multi-account traps: Lambda, VPC endpoints, and IAM conditions
A few environment-specific setups add their own wrinkles worth knowing before you go chasing a code bug that isn't actually there.
Lambda functions with a table name from an environment variable. It's common to inject the DynamoDB table name into a Lambda function via an environment variable set by your infrastructure template, so the same function code can run against a dev table and a prod table without changes. If that environment variable is missing, blank, or pointed at a table that was deleted and recreated with a different key schema during a deploy, the function will build a perfectly valid-looking key and send it to a table that doesn't match it — same error, but the fix is checking the Lambda console's configuration tab, not the handler code.
VPC gateway endpoints. If your Lambda function or EC2 instance reaches DynamoDB through a VPC gateway endpoint rather than the public internet, know that DynamoDB itself does not support resource-based policies on individual tables — access is controlled entirely through the endpoint policy attached to the VPC endpoint and the IAM policies attached to your users and roles. A restrictive endpoint policy can block a request entirely, but when it does, the error you'll see is an access-denied style failure, not this ValidationException. If you're troubleshooting a key mismatch and also see access-denied errors mixed in, treat them as two separate problems — fixing the endpoint policy won't touch the schema issue, and fixing the schema issue won't touch the endpoint policy.
Fine-grained IAM conditions on the partition key. DynamoDB supports IAM policy conditions like dynamodb:LeadingKeys, which restrict a caller to only accessing items whose partition key matches a specific value — commonly used so that each user can only read their own data in a shared table. If a caller's key is correctly shaped but doesn't match the value that condition requires, DynamoDB denies the request — again, as an access-control failure, not a schema-validation one. It's worth knowing this condition exists specifically so you don't waste time re-checking your key's data type and attribute name when the actual problem is a permissions boundary working exactly as designed.
🙋♂️ Jake's Reality Check
"One of my part-timers set up a test Lambda last week and now he's getting a totally different error — something about access denied. Is that the same bug?"
Almost certainly not. An access-denied error is a permissions problem — an IAM policy or VPC endpoint policy blocking the call — not a key-schema problem. They can show up around the same time during a deploy, but they need separate fixes.
Tools that make the schema easier to see
For a quick, one-off check, the AWS CLI's describe-table command is genuinely all you need — it's fast, it's scriptable, and it gives you the exact JSON DynamoDB itself considers authoritative. Don't reach for a heavier tool just to answer "what's my key schema" once.
Where a visual tool earns its place is when you're actively designing or reshaping a table's access patterns, not just checking one. AWS provides NoSQL Workbench for DynamoDB, a free, cross-platform desktop application that lets you design tables and indexes, define sample data, and — most usefully for this kind of debugging — build and run GetItem, Query, and other operations against real or local data through a visual operation builder, generating working sample code in multiple languages once you've confirmed the shape that actually works. It also bundles DynamoDB Local, so you can validate a key structure without touching a real AWS account at all.
Third-party DynamoDB GUI clients exist too, and some teams prefer them for day-to-day browsing of table contents. They're a reasonable choice for exploring data, but for the specific job of confirming a key schema, they're not doing anything the AWS CLI or NoSQL Workbench doesn't already do for free, straight from AWS's own tooling — so unless your team already has one installed and everyone's comfortable in it, it's not worth adding a new tool to your stack just to solve this particular error.
Before you paste a failing key into a support ticket or a log
Debugging this error usually means logging the exact key object your code built, and sometimes sharing that log with a teammate or in a support ticket. Before you do, take a second look at what's actually in that key. Partition keys are very often things like an email address, a phone number, or a customer ID that maps directly to a real person — precisely the kind of value that shouldn't end up sitting in a shared Slack channel, a public GitHub issue, or an unencrypted log file that outlives the debugging session by months.
⚠️ What this actually breaks
A debug log line that prints the full key object before every call is a normal, sensible troubleshooting step — but leaving that log statement in place after the bug is fixed, especially if it's writing to a log group with broad read access, means every future lookup's key value sits there in plain text indefinitely. Redact or remove it once the fix ships.
The same caution applies to CloudTrail. DynamoDB integrates with CloudTrail, which captures API calls made to DynamoDB — including calls from the console and code calls using both the classic API and PartiQL — as events, and if you turn on data event logging specifically, it can capture item-level activity too. That's genuinely useful for an audit trail of who changed what and when, but it means the same care about what ends up in a key value applies there as well: if you're enabling detailed data-event logging for troubleshooting, know that it's now part of your audit surface, with the retention and access-control implications that come with it.
Catching this before it reaches production
For a team that hits this error more than once, a bit of automation is worth the setup time:
- Add a schema-shape integration test. Point a test suite at DynamoDB Local, run your real key-building function against it, and assert that a basic
GetItemorPutItemsucceeds. This catches a name or type mismatch the moment it's introduced, in CI, rather than the first time a real user hits it. - Treat CloudTrail as your after-the-fact record, not your first line of defense. Because CloudTrail captures every DynamoDB API call, including its outcome, you can look back through it to confirm exactly when a particular pattern of failing calls started — genuinely useful for correlating "this began right after that deploy," but it's a diagnostic tool for after something has already gone wrong, not a preventer.
- Use CloudWatch Contributor Insights for DynamoDB on high-traffic tables and indexes to see which keys are being accessed and, in its dedicated throttled-keys mode, which ones are hitting capacity limits. It's aimed at throttling and access-pattern analysis rather than schema validation specifically, but it's a useful companion once the immediate schema bug is fixed, for spotting the next class of problem before it becomes urgent.
Decision table: match your symptom to the fix
| What you're seeing | Most likely cause | Where to look |
|---|---|---|
| Fails on every ID, no exceptions | Wrong data type (S vs N) | Cause 1 |
| Worked yesterday, broke today, no code changes | Table replaced by infra deploy, or wrong environment | Cause 5, IaC drift |
| Only fails on UpdateItem/DeleteItem, GetItem works fine | Extra attribute leaking into the Key object | Cause 4 |
| Started right after an SDK upgrade | Mixed low-level and document-client marshalling | Cause 6 |
| Trying to get "all records for this customer" | Using GetItem where Query is needed | Cause 7 |
| Mixed in with "access denied" errors | Separate IAM or VPC endpoint policy issue | Serverless traps |
Stopping this from coming back
Once you've fixed the immediate mismatch, a few habits keep it from resurfacing:
- Centralize your key-building logic. Write one function that builds a
Keyobject for a given table, and call it everywhere, instead of constructing key objects inline at every call site. One place to fix beats a dozen scattered copies. - Use a document client where your SDK offers one, so you're working with native types and letting the marshalling logic — code AWS maintains and tests, not code you wrote yourself — handle the wrapper objects.
- Add a type check or schema validator at the boundary where external input (a URL parameter, a form field, a message queue payload) becomes a DynamoDB key value — this is where strings that should be numbers most often sneak in.
- Treat key schema changes in infrastructure code as breaking changes that require coordinated deploys of both infrastructure and application code, not routine tweaks.
Ethan's take on this, once Jake's repair-lookup screen was working again: "The error message is actually one of the more honest ones DynamoDB gives you. It's not vague — it's telling you the shape is wrong. Most of the time people don't believe it, because they're staring at a value that looks correct, and the value is correct. It's just wearing the wrong label." Jake's response was a little less philosophical: he taped a printed copy of the table's key schema to the wall above his register, next to the receipt printer that jams every single Saturday without fail.
Frequently asked questions
What's the difference between this ValidationException and a ResourceNotFoundException?
A ResourceNotFoundException means the table or index name itself doesn't exist — DynamoDB can't even find the table you named. A ValidationException for a key mismatch means the table exists, but the Key object you sent doesn't match that table's schema in name, type, or count of attributes. One is "wrong address," the other is "right address, wrong form filled out."
Why does my key work fine in the AWS console but fail from my application code?
The console's item explorer builds the key request for you based on the table's actual schema, so it's always correctly shaped. Your application code has to build that same shape by hand (or via an SDK), and that's where a wrong type or a misspelled attribute name gets introduced. The console working isn't proof your code is right — it's proof the table itself is fine.
Can this error happen with PartiQL statements instead of the classic API calls?
Yes. DynamoDB's PartiQL support (ExecuteStatement, SELECT ... WHERE) still enforces the same underlying key schema rules for any statement that targets a specific primary key. Combining legacy parameters with expression-based parameters in the same call is also explicitly disallowed and returns its own ValidationException, separate from a key mismatch, so if you're on PartiQL and seeing validation errors, check whether you're mixing parameter styles as well as checking the key itself.
Does GetItem search Global Secondary Indexes automatically?
No. GetItem only ever looks at the base table's primary key. To retrieve items by a GSI's key attributes, you need Query with the IndexName parameter set to that index's name.
Why do I only get this error on UpdateItem and DeleteItem, but GetItem works?
This pattern usually points to an extra attribute leaking into the Key parameter specifically on your update and delete code paths — often because those functions reuse a full item object (built for a PutItem call elsewhere) instead of constructing a clean key-only object. Check what's actually in the object you're passing as Key on those two calls specifically.
Is "partition key" the same thing as "primary key"?
Not quite. The primary key is the whole thing DynamoDB uses to uniquely identify an item — it's either just the partition key (a "simple" primary key) or the partition key plus a sort key together (a "composite" primary key). The partition key alone, on a composite-key table, is not enough to uniquely identify a single item; it's only enough to identify a group of items that share that partition key value.
What happens if I accidentally send a number as a string, or vice versa?
DynamoDB rejects the request outright with this exact ValidationException. It does not attempt to coerce or auto-convert the type for you, no matter how numeric the string looks. The fix has to happen in your code, converting the value to the correct type before it's sent.
Can DynamoDB Local behave differently from the real AWS service on this?
DynamoDB Local is meant to mirror the real service's API behavior closely, so a genuine key schema mismatch should fail the same way in both. Where the two commonly diverge in practice isn't the validation logic itself — it's that a local table definition file gets out of sync with whatever the real table's schema has become, so you end up comparing your code against the wrong schema locally, not against a different set of DynamoDB rules.
This started right after I migrated to AWS SDK for JavaScript v3 — why?
v3 split the old v2 AWS.DynamoDB.DocumentClient class into a separate package, @aws-sdk/lib-dynamodb, layered on top of the low-level @aws-sdk/client-dynamodb. If your migration imported the low-level client but left old document-client-style code in place (plain native values, no explicit {"S":...} wrappers), or the reverse, the mismatch between what the client expects and what your code sends will surface as exactly this error. Confirm which client class you're actually instantiating.
Does capitalization matter in DynamoDB attribute names?
Yes, entirely. All DynamoDB names — tables, attributes, indexes — are case-sensitive. OrderId and orderId are two different attributes as far as DynamoDB is concerned, and using the wrong case for a key attribute produces this same mismatch error.
Can Terraform or CloudFormation cause this error even without a code change?
Yes. Because key schema changes typically force a table replacement rather than an in-place update, an infrastructure deploy that renames or retypes a key attribute can create a brand-new table with a different schema while your already-deployed application code still expects the old one. No application code changed, but the table underneath it did.
Why does BatchWriteItem sometimes fail some items but not others, instead of erroring like this?
That's a different mechanism — throttling, not a schema mismatch. If DynamoDB can't process every item in a batch due to capacity limits, it returns the unprocessed ones in an UnprocessedItems field for you to retry, and the call as a whole still succeeds. A genuine key schema mismatch on any item fails the entire batch request with a ValidationException instead — it doesn't quietly skip just that item.
Is there a way to see my table's exact key schema without opening the AWS console?
Run aws dynamodb describe-table --table-name YourTableName with the AWS CLI. The response includes a KeySchema block naming each key attribute and whether it's the partition key (HASH) or sort key (RANGE), plus an AttributeDefinitions block giving each one's exact data type.
Can a Global Secondary Index throw this same error?
An index has its own KeySchemaElement entries, separate from the base table's, and those have to be scalar top-level attributes of type String, Number, or Binary. A Query against an index that references the wrong attribute name for that index's own key schema produces the same family of key-schema validation failure, scoped to the index.
Why does this only fail on some items, not all of them?
If the failure is intermittent rather than constant, the likeliest explanation isn't the schema itself — it's inconsistent data going into your key-building code. A mix of numeric and non-numeric IDs in the same field, some records missing a sort-key value that others have, or a code path that only sometimes appends an extra attribute, will all produce a mismatch on some items and not others. Compare a failing ID against a succeeding one, attribute by attribute, rather than assuming the whole table's schema is inconsistent — DynamoDB's schema itself can't vary from item to item; only your request-building logic can.
Should I add validation before every DynamoDB call to catch this earlier?
It's a reasonable investment if your application talks to DynamoDB from many places in the codebase — a small schema-checking utility that confirms a key object's attribute names and types before the call goes out can turn a production error into a local, immediate one. It's not strictly required for small applications with one or two call sites, where centralizing the key-building logic into a single function, as described above, gets you most of the same protection with less overhead.
Revision note. Written September 2026. This will need a fresh look whenever AWS changes how a specific SDK's document client handles marshalling, or introduces new validation behavior for PartiQL statements. If you're staring at this error at 11pm with a customer waiting, take a breath — it's one of the more fixable ones DynamoDB throws, and you're closer to done than it feels right now.