floci-io/floci · error · AwsException

InvalidNextTokenException

InvalidNextTokenException

Error message

Invalid pagination token

What it means

ACM InvalidNextTokenException (HTTP 400) thrown by AcmService.decodeToken when a ListCertificates NextToken cannot be Base64-decoded or does not contain the expected embedded \"lastArn\":\"...\" JSON field. Floci encodes pagination cursors as Base64 JSON carrying the last-seen ARN; any token not produced by this emulator's listCertificates call is rejected.

Source

Thrown at src/main/java/io/github/hectorvent/floci/services/acm/AcmService.java:304

    private String encodeToken(String lastArn) {
        if (lastArn == null) return null;
        String json = "{\"lastArn\":\"" + lastArn + "\"}";
        return Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * Decodes a pagination cursor from Base64 JSON.
     */
    private String decodeToken(String token) {
        if (token == null || token.isEmpty()) return null;
        try {
            String json = new String(Base64.getDecoder().decode(token), StandardCharsets.UTF_8);
            // Simple JSON parsing without Jackson dependency in this method
            int start = json.indexOf("\"lastArn\":\"") + 11;
            int end = json.indexOf("\"", start);
            return json.substring(start, end);
        } catch (Exception e) {
            throw new AwsException("InvalidNextTokenException", "Invalid pagination token", 400);
        }
    }

    // ============ DeleteCertificate ============

    public void deleteCertificate(String certificateArn, String region) {
        Certificate cert = getCertificateByArn(certificateArn, region);

        if (cert.getInUseBy() != null && !cert.getInUseBy().isEmpty()) {
            throw new AwsException("ResourceInUseException",
                "Certificate " + certificateArn + " is in use by: " + String.join(", ", cert.getInUseBy()), 409);
        }

        String storageKey = regionKey(region, cert.extractCertificateId());
        store.delete(storageKey);
        LOG.infov("Deleted certificate: {0}", certificateArn);
    }

View on GitHub (pinned to 62ff490619)

Solutions

  1. Treat NextToken as opaque and short-lived: obtain it from the immediately preceding listCertificates response and pass it back verbatim
  2. If paginating across emulator restarts or storage clears, restart pagination from page one instead of reusing an old token
  3. Verify the token is not mangled in transit (shell quoting, URL encoding, log truncation) before sending
  4. Ensure you are hitting the same emulator endpoint/region that issued the token

Example fix

// before: reusing a token from a previous run / different endpoint
var resp1 = acm.listCertificates(r -> r.maxItems(10));
var resp2 = acm.listCertificates(r -> r.maxItems(10).nextToken(savedToken));

// after: always chain from the latest response
var resp = acm.listCertificates(r -> r.maxItems(10));
String token = resp.nextToken();
while (token != null) {
    final String t = token;
    resp = acm.listCertificates(r -> r.maxItems(10).nextToken(t));
    token = resp.nextToken();
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
    var page = acm.listCertificates(r -> r.maxItems(10).nextToken(token));
} catch (InvalidNextTokenException e) {
    // restart pagination from the first page
    token = null;
}

Prevention

When it happens

Trigger: Calling acm.listCertificates(request -> request.nextToken(...)) with a token that is malformed, truncated, URL-escaped twice, copied from a different emulator run (storage was reset between calls), hand-crafted, or from a real AWS account. Any exception during Base64 decode or the substring extraction lands in the same catch.

Common situations: Persisting NextToken across emulator restarts (in-memory storage lost, token no longer valid), passing an XML/JSON-escaped token through a shell without quoting, mixing tokens between environments (local Floci vs real AWS), or token field-name drift after refactors.

Related errors


AI-assisted analysis of floci-io/floci@62ff490619 (2026-08-14). Data as JSON: /api/errors/c9b9e2ee8d2ada8b. Report an issue: GitHub.