{"record":{"id":"c9b9e2ee8d2ada8b","repo":"floci-io/floci","slug":"invalidnexttokenexception","errorCode":"InvalidNextTokenException","errorMessage":"Invalid pagination token","messagePattern":"Invalid pagination token","errorType":"http","errorClass":"AwsException","httpStatus":400,"severity":"error","filePath":"src/main/java/io/github/hectorvent/floci/services/acm/AcmService.java","lineNumber":304,"sourceCode":"    private String encodeToken(String lastArn) {\n        if (lastArn == null) return null;\n        String json = \"{\\\"lastArn\\\":\\\"\" + lastArn + \"\\\"}\";\n        return Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8));\n    }\n\n    /**\n     * Decodes a pagination cursor from Base64 JSON.\n     */\n    private String decodeToken(String token) {\n        if (token == null || token.isEmpty()) return null;\n        try {\n            String json = new String(Base64.getDecoder().decode(token), StandardCharsets.UTF_8);\n            // Simple JSON parsing without Jackson dependency in this method\n            int start = json.indexOf(\"\\\"lastArn\\\":\\\"\") + 11;\n            int end = json.indexOf(\"\\\"\", start);\n            return json.substring(start, end);\n        } catch (Exception e) {\n            throw new AwsException(\"InvalidNextTokenException\", \"Invalid pagination token\", 400);\n        }\n    }\n\n    // ============ DeleteCertificate ============\n\n    public void deleteCertificate(String certificateArn, String region) {\n        Certificate cert = getCertificateByArn(certificateArn, region);\n\n        if (cert.getInUseBy() != null && !cert.getInUseBy().isEmpty()) {\n            throw new AwsException(\"ResourceInUseException\",\n                \"Certificate \" + certificateArn + \" is in use by: \" + String.join(\", \", cert.getInUseBy()), 409);\n        }\n\n        String storageKey = regionKey(region, cert.extractCertificateId());\n        store.delete(storageKey);\n        LOG.infov(\"Deleted certificate: {0}\", certificateArn);\n    }\n","sourceCodeStart":286,"sourceCodeEnd":322,"githubUrl":"https://github.com/floci-io/floci/blob/62ff490619e7bd3554597c28c704081b4c15add5/src/main/java/io/github/hectorvent/floci/services/acm/AcmService.java#L286-L322","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: reusing a token from a previous run / different endpoint\nvar resp1 = acm.listCertificates(r -> r.maxItems(10));\nvar resp2 = acm.listCertificates(r -> r.maxItems(10).nextToken(savedToken));\n\n// after: always chain from the latest response\nvar resp = acm.listCertificates(r -> r.maxItems(10));\nString token = resp.nextToken();\nwhile (token != null) {\n    final String t = token;\n    resp = acm.listCertificates(r -> r.maxItems(10).nextToken(t));\n    token = resp.nextToken();\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    var page = acm.listCertificates(r -> r.maxItems(10).nextToken(token));\n} catch (InvalidNextTokenException e) {\n    // restart pagination from the first page\n    token = null;\n}","preventionTips":["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"],"tags":["acm","pagination","next-token","validation","aws"],"backgroundTag":null,"analyzedSha":"62ff490619e7bd3554597c28c704081b4c15add5","analyzedAt":"2026-08-14T14:25:23.764Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}