DynamoDB Query in Java (AWS SDK v2)

queryPaginator im AWS SDK für Java 2.x sieht aus wie eine Collection und ist keine. Es ist ein Lazy-Re-Iterable, und der Unterschied zeigt sich auf deiner Rechnung, sobald du ein zweites Mal darüber iterierst. Wann Query überhaupt der richtige Read ist, steht in Query vs. Scan.

Code

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.QueryRequest;
import software.amazon.awssdk.services.dynamodb.model.QueryResponse;

public class QueryExample {
    public static void main(String[] args) {
        try (DynamoDbClient ddb = DynamoDbClient.builder()
                .region(Region.US_EAST_1)
                .build()) {

            Map<String, String> names = new HashMap<>();
            names.put("#hashKey", "Artist");
            names.put("#rangeKey", "SongTitle");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":hashKeyValue", AttributeValue.builder().s("Arturo Sandoval").build());
            values.put(":rangeKeyValue", AttributeValue.builder().s("C").build());

            QueryRequest request = QueryRequest.builder()
                    .tableName("Music")
                    .keyConditionExpression(
                            "#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)")
                    .expressionAttributeNames(names)
                    .expressionAttributeValues(values)
                    .build();

            List<Map<String, AttributeValue>> items = new ArrayList<>();
            for (QueryResponse page : ddb.queryPaginator(request)) {
                items.addAll(page.items());
            }
            System.out.println("Found " + items.size() + " items");
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
        }
    }
}

QueryIterable führt die Abfrage bei jeder Iteration erneut aus

Gegen ein Fixture aus 600 Songs, jeder ~3,9 KB und alle unter Artist = "Arturo Sandoval", kam ddb.queryPaginator(request) in 2,6 ms zurück und schickte nichts. Dann wurde dasselbe QueryIterable-Objekt zweimal durchlaufen:

queryPaginator(request) returned in 2.60 ms   (class QueryIterable, 0 requests)
iteration 1: pages=3  items=600  capacity=284.5
iteration 2: pages=3  items=600  capacity=284.5   <- same object

Zwei for-Schleifen über einer Variablen, 569 Leseeinheiten. Die Seiten wurden nie zwischengespeichert; jede Iteration läuft LastEvaluatedKey wieder von vorn ab. Wenn du die Items zweimal brauchst, leere das Iterable einmal in eine List, so wie es das Beispiel tut.

Die Kehrseite derselben Lazyness ist die Stelle, an der Fehler auftauchen. Bau eine Anfrage, deren Key Condition den Partition Key auslässt, und queryPaginator nimmt sie klaglos an, weil noch kein Aufruf stattgefunden hat:

queryPaginator(bad) constructed without throwing
threw on iteration: DynamoDbException / ValidationException /
Query condition missed key schema element / http 400

Ein try-catch um den Builder herum fängt nichts. Es muss die Schleife umschließen — deshalb steckt im Beispiel der ganze Block in einem try.

Am 2026-07-28 gegen DynamoDB Local (amazon/dynamodb-local) mit software.amazon.awssdk:dynamodb 2.49.4 auf OpenJDK 26.0.1 gemessen.

Erklärung

  • ddb.queryPaginator(request).items() flacht die Seiten zu einem Iterable<Map<String, AttributeValue>> ab und paginiert hinter deinem Rücken — die zweistufige Schleife im Beispiel schrumpft also auf eine, wenn du nur die Items willst. Es ist außerdem ein SdkIterable, .stream() funktioniert also.
  • Zahlen sind String im Builder. AttributeValue.builder().n("1994") ist kein Tippfehler für .n(1994) — der n()-Setter nimmt einen java.lang.String, weil DynamoDB Zahlen als Dezimaltext transportiert, um binäre Float-Rundung zu vermeiden. Ein Java-int zu übergeben kompiliert nicht.
  • keyConditionExpression nimmt eine Gleichheit auf dem Partition Key plus höchstens eine Sort-Key-Bedingung (=, <, <=, >, >=, BETWEEN, begins_with); .scanIndexForward(false) dreht die Reihenfolge um und .indexName("...") richtet sie auf einen Sekundärindex.
  • Der Enhanced Client ist die andere Ergonomie. software.amazon.awssdk.enhanced.dynamodb mappt annotierte Beans statt Map<String, AttributeValue>, und sein query liefert ein PageIterable<T> mit derselben oben gemessenen Lazy-Re-Iteration-Semantik.

Mach es visuell

Sowohl #hashKey als auch #rangeKey sind in diesem Beispiel Aliase, zu denen dich nichts zwingt: Weder Artist noch SongTitle steht auf AWS' 573 Wörter langer Liste reservierter Wörter. Der Reserved-Words-Checker sagt dir, welche deiner Attributnamen die #-Behandlung wirklich brauchen — damit die Alias-Map kein Cargo-Kult mehr ist.

Um eine Key Condition gegen eine echte Tabelle auszuprobieren, bevor du den QueryRequest baust, lade DynoTable herunter und blättere durch die Ergebnisse in einem Grid.

Verwandte Beispiele

Referenzen

Diesen Request visuell bauen

Stelle diese Operation im kostenlosen DynamoDB Query Builder zusammen — Key-Bedingung, Filter, Index, Limit, Sortierreihenfolge und eine Paginierungsschleife — und kopiere sie als lauffähiges SDK-v3-, CLI- oder boto3-Programm zurück.

DynamoDB Query Builder öffnen

Mit DynamoDB ohne die Console arbeiten

Ein schneller DynamoDB-Desktop-Client, der das echte SQL ausführt, das DynamoDB nicht kann — JOINs, GROUP BY, Aggregationen — mit visueller Bearbeitung und einem KI-Agenten auf deinen eigenen Bedrock-Schlüsseln.

30 Tage kostenlos testen, keine Kreditkarte — danach der Kostenlos-Tarif ohne Zeitlimit.