DynamoDB Scan in Java (AWS SDK v2)

scanPaginator erledigt LastEvaluatedKey für dich — und der eine Regler, zu dem die meisten als Nächstes greifen, .limit(...), macht denselben Scan langsamer und etwas teurer. Wann du Scan ganz vermeiden solltest, 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.ScanRequest;
import software.amazon.awssdk.services.dynamodb.model.ScanResponse;

public class ScanExample {
    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("#filter0", "Year");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":filterValue0", AttributeValue.builder().n("2010").build());

            ScanRequest request = ScanRequest.builder()
                    .tableName("Music")
                    .filterExpression("#filter0 >= :filterValue0")
                    .expressionAttributeNames(names)
                    .expressionAttributeValues(values)
                    .build();

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

.limit(25) macht aus 3 Requests 25 — und kostet mehr

Das Fixture hält 600 Songs von jeweils rund 3,9 KB, 8 davon treffen zu. Führe das Beispiel wie geschrieben aus und dann noch einmal mit .limit(25):

Request-FormRoundtripsLeseeinheitenZurückgegebene Items
wie geschrieben3284,58
.limit(25)25288,08

Kapazität wird pro Seite abgerechnet, aufgerundet auf eine 4-KB-Grenze — ein 1-MB-Lesevorgang in 24 kleine zu zerschneiden bezahlt die Aufrundung also 24 Mal. Der 25. Request ist die andere Überraschung: Seite 24 hat die Tabelle beendet und trotzdem einen LastEvaluatedKey zurückgegeben, der Paginator hat also noch einmal gefragt und scannedCount=0 bekommen. DynamoDB signalisiert „keine weiteren Daten", indem es diesen Key weglässt, nicht durch eine kurze Seite — und an einer Limit-Grenze weiß es das noch nicht.

.limit(...) ist ein Regler zur Kapazitätsglättung für einen Hintergrundjob, der eine Live-Tabelle nicht drosseln soll. Es ist kein Weg, einen Scan günstiger oder kürzer zu machen.

Der Alias ist keine Stilfrage

Lass #filter0 weg und filtere direkt auf Year, und das SDK meldet über awsErrorDetails() das hier:

DynamoDbException / ValidationException /
Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year
/ http 400

Year ist eines von 573 reservierten Wörtern. Wie jeder Paginator-Fehler in diesem SDK kommt er bei der ersten Iteration der for-Schleife an, nicht beim Aufruf von scanPaginator(request) — das try muss die Schleife also umschließen.

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

  • Die ersten beiden Seiten geben null Items zurück. Mit diesem Filter lauten die Ergebnisse pro Seite 0, 0 und 8, bei 128,5, 128,5 und 27,5 Leseeinheiten. filterExpression läuft nach dem Lesen, diese beiden leeren Antworten kosten also den vollen Preis, und jedes if (page.items().isEmpty()) break meldet eine leere Tabelle.
  • ddb.scanPaginator(request).items() flacht die Seiten zu einem einzelnen Iterable<Map<String, AttributeValue>> ab und blättert hinter dir her, was die verschachtelte Schleife zusammenfallen lässt, wenn du nur Items willst. Es ist ein SdkIterable, .stream() funktioniert also — .items().stream().count() gibt hier 8 zurück.
  • ScanIterable führt den Scan bei jeder Iteration erneut aus. Es ist lazy, nicht gecacht: Zweimal über dasselbe Objekt zu laufen schickt die Requests zweimal und rechnet zweimal ab. Leere es einmal in eine List, so wie das Beispiel es tut.
  • Zahlen sind im Builder String. AttributeValue.builder().n("2010") nimmt einen java.lang.String, denn DynamoDB transportiert Zahlen als Dezimaltext. Ein int zu übergeben kompiliert nicht.
  • .segment(...) / .totalSegments(...) teilen einen Full-Table-Scan auf Worker auf, jeder mit eigenem Paginator. Das teilt die Wanduhrzeit und verbraucht dieselbe Kapazität.

Mach es visuell

Der Reserved-Words-Checker prüft deine Attributnamen gegen die vollständige AWS-Liste und gibt dir die ExpressionAttributeNames-Map zurück — schneller, als eine ValidationException nach der anderen zu lernen, dass Year, Name, Size und Status alle vergeben sind.

Um einen Filter gegen eine echte Tabelle auszuprobieren, bevor du ihn in einen ScanRequest einbaust, lade DynoTable herunter und blättere die Ergebnisse in einem Grid durch.

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.