DEV Community

Cover image for How to Handle DynamoDB Reserved Keywords in Java (SDK v1 and v2)
Axel Dlv for AWS Community Builders

Posted on

How to Handle DynamoDB Reserved Keywords in Java (SDK v1 and v2)

TL;DR - Handling DynamoDB Reserved Keywords

The Problem: DynamoDB has reserved keywords (like order, name, status) that can't be used directly in queries, causing AmazonDynamoDBException errors.

The Solution: Use Expression Attribute Names as placeholders

  • Replace reserved words with placeholders starting with # (e.g., #ord for order)
  • Map the placeholder to the actual attribute name
  • Use in your filter expression: #ord = :val

Example:

// Instead of: "order = :val"
// Use: "#ord = :val"
Map<String, String> expressionAttributeNames = Map.of("#ord", "order");
Enter fullscreen mode Exit fullscreen mode

Best Practices:

  1. Always use placeholders defensively to avoid issues
  2. Or design tables to avoid reserved words from the start (use prefixes like item_order or PascalCase)

Note: The article uses scan() which is expensive. Consider adding a Global Secondary Index (GSI) if you frequently query by a specific attribute.


When working with Amazon DynamoDB, it's easy to run into one of its many reserved keywords — words you can't use directly as attribute names in expressions.

Recently, I stumbled upon this issue while trying to query a table that had an attribute named order.

DynamoDB didn't like that, here's how I fixed it.

The Problem

Let's say you have a DynamoDB table mapped to a Template entity, and one of its attributes is named order.

You might try to scan it like this (AWS SDK v1):

DynamoDBScanExpression scanExpression = new DynamoDBScanExpression()
    .withFilterExpression("order = :val")
    .withExpressionAttributeValues(Map.of(":val", new AttributeValue().withN("1")))
    .withLimit(1);
Enter fullscreen mode Exit fullscreen mode

But this will throw an AmazonDynamoDBException complaining that order is a reserved keyword.

DynamoDB reserves certain words (like size, name, order, status, etc.) for its own use — so you can't use them directly in filter or key expressions.

Performance Note: scan() is expensive and reads the entire table. If you frequently query by order, consider adding a Global Secondary Index (GSI) to use query() instead of scan().

The Solution: Expression Attribute Names

The trick is to use Expression Attribute Names, which let you create a "placeholder" for your reserved word.

Here's a working version of the same method:

AWS SDK v1 (DynamoDBMapper)

public Optional getByOrder(final Integer order) {
    if (order == null) {
        throw new IllegalArgumentException("Order must not be null");
    }

    // Map for replacing reserved attribute names
    final Map expressionAttributeNames = 
            Collections.singletonMap("#ord", "order");

    // Map for attribute values
    final Map expressionAttributeValues = 
            Collections.singletonMap(":val", new AttributeValue().withN(order.toString()));

    // Define the ScanExpression with limit
    final DynamoDBScanExpression scanExpression = new DynamoDBScanExpression()
            .withFilterExpression("#ord = :val")
            .withExpressionAttributeNames(expressionAttributeNames)
            .withExpressionAttributeValues(expressionAttributeValues)
            .withLimit(1);

    // Execute scan and return the first result if exists
    final PaginatedScanList scanResult = dynamoDBMapper.scan(Template.class, scanExpression);

    return scanResult.isEmpty() ? Optional.empty() : Optional.of(scanResult.get(0));
}
Enter fullscreen mode Exit fullscreen mode

AWS SDK v2

public Optional getByOrder(Integer order) {
    if (order == null) {
        throw new IllegalArgumentException("Order must not be null");
    }

    // Get the DynamoDB table reference
    DynamoDbTable table = enhancedClient.table("Template", TableSchema.fromBean(Template.class));

    // Build expression with attribute name placeholder to handle reserved keyword "order"
    // #ord is a placeholder for the reserved word "order"
    // :val is a placeholder for the actual value to compare
    Expression expression = Expression.builder()
            .expression("#ord = :val")
            .expressionNames(Map.of("#ord", "order"))  // Map placeholder to actual attribute name
            .expressionValues(Map.of(":val", AttributeValue.builder().n(order.toString()).build()))
            .build();

    // try-with-resources ensures PageIterable is properly closed to avoid resource leaks
    try (var pages = table.scan(ScanEnhancedRequest.builder()
            .filterExpression(expression)
            .build())) {

        // Return the first matching item, if any
        return pages.items().stream().findFirst();
    }
}
Enter fullscreen mode Exit fullscreen mode

What's Happening Here

  • #ord is a placeholder for the real attribute name order. You can name it anything as long as it starts with a #. Then, in the expressionAttributeNames map, you define what #ord actually represents.

  • :val is a placeholder for the value you're comparing against — this is standard in DynamoDB filter expressions.

  • Finally, the filter expression #ord = :val safely compares the order field without triggering a reserved word error.

Recommended Strategies

Option 1: Always use placeholders (defensive approach)

// Even if it's not a reserved word, use placeholders
.withExpressionAttributeNames(Map.of("#attr", "myAttribute"))
Enter fullscreen mode Exit fullscreen mode

Option 2: Design - Avoid reserved words from the start

  • Use prefixes: item_order, user_status, product_name
  • PascalCase: OrderValue, StatusCode

Conclusion

Whenever DynamoDB throws an error like:

"Invalid KeyConditionExpression: Attribute name is a reserved keyword"

Just use Expression Attribute Names to "escape" the reserved word.

It's clean, simple, and works everywhere — filters, updates, and queries.


Photo by Alireza Akhlaghi on Unsplash

Top comments (0)