floci-io/floci · error · AwsException

InvalidIfMatchVersion

InvalidIfMatchVersion

Error message

The If-Match version is missing or not valid for the resource.

What it means

Floci's CloudFront service layer compares the caller's If-Match value against the distribution's stored ETag on UpdateDistribution. Unlike the controller-side checks (missing header), this fires when a header WAS sent but does not equal the current ETag — the emulator rotates the ETag (UUID) on every update, so any stale value fails.

Source

Thrown at src/main/java/io/github/hectorvent/floci/services/cloudfront/CloudFrontService.java:142

        dist.setLastModifiedTime(Instant.now());
        dist.setEtag(UUID.randomUUID().toString());
        if (tags != null && !tags.isEmpty()) {
            dist.setTags(tags);
            tagStore.put("distribution/" + id, tags);
        }
        distStore.put(id, dist);
        return dist;
    }

    public Distribution getDistribution(String id) {
        return distStore.get(id).orElseThrow(() ->
                new AwsException("NoSuchDistribution", "The specified distribution does not exist.", 404));
    }

    public synchronized Distribution updateDistribution(String id, String ifMatch, Distribution updated) {
        Distribution existing = getDistribution(id);
        if (!existing.getEtag().equals(ifMatch)) {
            throw new AwsException("InvalidIfMatchVersion",
                    "The If-Match version is missing or not valid for the resource.", 400);
        }
        ensureAliasesAvailable(updated.getConfig(), id);
        validateTrustedKeyGroups(updated.getConfig());
        updated.setId(id);
        updated.setArn(existing.getArn());
        updated.setDomainName(existing.getDomainName());
        updated.setStatus("Deployed");
        updated.setLastModifiedTime(Instant.now());
        updated.setEtag(UUID.randomUUID().toString());
        updated.setTags(existing.getTags());
        distStore.put(id, updated);
        return updated;
    }

    public synchronized void deleteDistribution(String id, String ifMatch) {
        Distribution existing = getDistribution(id);
        if (!existing.getEtag().equals(ifMatch)) {

View on GitHub (pinned to 62ff490619)

Solutions

  1. Re-GET the distribution config immediately before each PUT and use the fresh ETag
  2. On this error, treat it as a conflict: reload the config, reapply your changes, and retry once
  3. Never reuse an ETag after a successful update — it is regenerated per write

Example fix

// before: etag cached once and reused
cf.updateDistribution(req -> req.id(id).ifMatch(cachedEtag).distributionConfig(cfg));

// after: refresh each time
String etag = cf.getDistributionConfig(r -> r.id(id)).eTag();
cf.updateDistribution(req -> req.id(id).ifMatch(etag).distributionConfig(cfg));
Defensive patterns

Strategy: retry

Validate before calling

// precondition: ETag must be the one from the most recent read
String current = cf.getDistributionConfig(r -> r.id(id)).eTag();
if (!current.equals(ifMatchValue)) {
    // stale: reload config and merge your changes before updating
}

Try / catch

for (int attempt = 0; attempt < 2; attempt++) {
    try {
        String etag = cf.getDistributionConfig(r -> r.id(id)).eTag();
        cf.updateDistribution(req -> req.id(id).ifMatch(etag).distributionConfig(cfg));
        break;
    } catch (CloudFrontException e) {
        if ("InvalidIfMatchVersion".equals(e.awsErrorDetails().errorCode()) && attempt == 0) {
            continue; // ETag raced; re-read and retry once
        }
        throw e;
    }
}

Prevention

When it happens

Trigger: PUT /distribution/{Id}/config with an If-Match value that differs from the stored ETag: reused ETag from an earlier update, ETag of a different distribution, or a value like '*' that the emulator does not special-case.

Common situations: Caching the ETag across multiple updates instead of re-reading after each one; concurrent writers where one update invalidates the other's ETag; assuming '*' is accepted as any-version.

Related errors


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