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
- Treat NextToken as opaque and short-lived: obtain it from the immediately preceding listCertificates response and pass it back verbatim
- If paginating across emulator restarts or storage clears, restart pagination from page one instead of reusing an old token
- Verify the token is not mangled in transit (shell quoting, URL encoding, log truncation) before sending
- 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
- Treat NextToken as opaque: pass it back verbatim from the previous response only
- Never persist NextTokens across emulator restarts or share them between regions/endpoints
- Quote tokens correctly through shells and logs to avoid truncation
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
- ValidationException
- IdempotencyException
- ResourceInUseException
- TooManyTagsException
- BadRequestException
AI-assisted analysis of floci-io/floci@62ff490619 (2026-08-14).
Data as JSON: /api/errors/c9b9e2ee8d2ada8b.
Report an issue: GitHub.