floci-io/floci · error · AwsException
IdempotencyException
IdempotencyException
Error message
An idempotency token was used with a request that does not match a previous request that used that token.
What it means
ACM IdempotencyException (HTTP 400) thrown during requestCertificate idempotency lookup: a stored token entry exists and is unexpired, but computeRequestHash of the incoming request (Objects.hash of domainName, SANs as a set, keyAlgorithm) differs from the hash recorded when the token was first used. ACM requires the same token be reused only with identical request parameters.
Source
Thrown at src/main/java/io/github/hectorvent/floci/services/acm/AcmService.java:524
* @throws AwsException if token exists but parameters don't match (IdempotencyTokenException)
*/
private Optional<Certificate> findByIdempotencyToken(String token, String region, int requestHash) {
String indexKey = region + "::" + token;
IdempotencyTokenEntry entry = idempotencyTokenIndex.get(indexKey);
if (entry == null) {
return Optional.empty();
}
// Lazy expiration: remove expired entries on lookup
if (entry.isExpired()) {
idempotencyTokenIndex.remove(indexKey);
return Optional.empty();
}
// Validate request parameters match
if (entry.requestHash() != requestHash) {
throw new AwsException("IdempotencyException",
"An idempotency token was used with a request that does not match a previous request " +
"that used that token.", 400);
}
String certId = extractCertificateIdFromArn(entry.arn());
return store.get(regionKey(region, certId));
}
/**
* Computes a hash of request parameters for idempotency validation.
* Parameters include: domainName, SANs (order-independent), keyAlgorithm.
*/
private int computeRequestHash(String domainName, List<String> sans, KeyAlgorithm keyAlgorithm) {
return Objects.hash(
domainName,
sans != null ? new HashSet<>(sans) : null,
keyAlgorithm
);View on GitHub (pinned to 62ff490619)
Solutions
- Reuse an idempotency token only for retries of the exact same request; generate a fresh token whenever parameters change
- Derive the token deterministically from the request content (e.g. UUID5/sha256 of domain+SANs+algorithm) so identical params yield identical tokens and differing params never collide
- In tests, use unique tokens per test case or reset the emulator between parameter-changing scenarios
Example fix
// before: same static token for different requests
acm.requestCertificate(r -> r.domainName("a.example.com").idempotencyToken("tok"));
acm.requestCertificate(r -> r.domainName("b.example.com").idempotencyToken("tok")); // IdempotencyException
// after: token derived from request content
String token = UUID.nameUUIDFromBytes((domain + "|" + String.join(",", sans) + "|" + alg).getBytes()).toString();
acm.requestCertificate(r -> r.domainName(domain).idempotencyToken(token)); Defensive patterns
Strategy: validation
Validate before calling
String token = UUID.nameUUIDFromBytes(
(domain + "|" + (sans == null ? "" : String.join(",", sans.stream().sorted().toList())) + "|" + alg)
.getBytes(StandardCharsets.UTF_8)).toString();
// identical params -> identical token; different params -> different token, no collision Try / catch
try {
acm.requestCertificate(r -> r.domainName(d).idempotencyToken(tok));
} catch (IdempotencyException e) {
// same token used with different params — mint a fresh token and retry
} Prevention
- Never reuse an idempotency token after changing any request parameter
- Derive tokens from request content so identity is deterministic
- Use unique tokens per test case; never hardcode 'token-1' across scenarios
When it happens
Trigger: requestCertificate(..., idempotencyToken=T) with domainName/SANs/keyAlgorithm differing from an earlier call that used the same token T within the token's validity window. Note SAN comparison is order-insensitive (HashSet), but adding/removing a SAN, changing the domain, or switching keyAlgorithm changes the hash.
Common situations: Retrying a failed request but with 'fixed' parameters and the same token; token generators derived from request count that collide across differing requests; test suites reusing hardcoded tokens ('token-1') across different certificate requests.
Related errors
- InvalidNextTokenException
- ValidationException
- ResourceInUseException
- TooManyTagsException
- BadRequestException
AI-assisted analysis of floci-io/floci@62ff490619 (2026-08-14).
Data as JSON: /api/errors/182fea0a8956d2b4.
Report an issue: GitHub.