Ryan M Ryan M - 2 months ago 18x
Java Question

DynamoDB Global Secondary Index with Exclusive Start Key

Is it possible to specify an exclusive start key when querying a DynamoDB table via global secondary index?

I'm using the aws-java-sdk version 1.6.10 and executing queries with a

and a
. Here's the gist of what I'm trying to do:

MappedItem key = new MappedItem();

Map<String, AttributeValue> exclusiveStartKey = new HashMap<String, AttributeValue>();
exclusiveStartKey.put(MappedItem.INDEXED_ATTRIBUTE_NAME, new AttributeValue().withS(attributeValue));
exclusiveStartKey.put(MappedItem.TIMESTAMP, new AttributeValue().withN(startTimestamp.toString()));

DynamoDBQueryExpression<MappedItem> queryExpression = new DynamoDBQueryExpression<MappedItem>();
queryExpression.setLimit(maxResults * 2);

This results in a 400 error saying that the specified start key is invalid. The TIMESTAMP is the range key for the table index and for the global secondary index, and the attribute value pair is valid (i.e. there is an item in the table with the values passed as the hash and range key for the index, and the attribute passed as the index is the hash key of the global secondary index).

Is there something I missed or is this not possible?


Per an Amazonian, this is not possible: https://forums.aws.amazon.com/thread.jspa?threadID=146102&tstart=0

A workaround that worked for my use case, though, was to just specify a RangeKeyCondition greater than the last retrieved object's timestamp. Here's the idea:

Condition hashKeyCondition = new Condition();
hashKeyCondition.withComparisonOperator(ComparisonOperator.EQ).withAttributeValueList(new AttributeValue().withS(hashKeyAttributeValue));

Condition rangeKeyCondition = new Condition();
rangeKeyCondition.withComparisonOperator(ComparisonOperator.GT).withAttributeValueList(new AttributeValue().withN(timestamp.toString()));

Map<String, Condition> keyConditions = new HashMap<String, Condition>();
keyConditions.put(MappedItem.INDEXED_ATTRIBUTE_NAME, hashKeyCondition);
keyConditions.put(MappedItem.TIMESTAMP, rangeKeyCondition);

QueryRequest queryRequest = new QueryRequest();

QueryResult result = amazonDynamoDBClient.query(queryRequest);

List<MappedItem> mappedItems = new ArrayList<MappedItem>();

for(Map<String, AttributeValue> item : result.getItems()) {
    MappedItem mappedItem = dynamoDBMapper.marshallIntoObject(MappedItem.class, item);

return mappedItems;

Note that the marshallIntoObject method is deprecated in favor of a protected method in the DynamoDBMapper class, but it's easy enough to write a marshaller were a future upgrade to break the mapping.

Not as elegant as using the mapper but it accomplishes the same thing.