floci-io/floci · error · AwsException

ConflictException

ConflictException

Error message

API with id '{apiId}' already exists

What it means

Thrown by ApiGatewayV2Service.createApi() when the API id that will be assigned already exists in the same region. Normally ids are freshly generated (shortId(10)), so collisions are effectively impossible; the realistic trigger is ReservedTags.extractOverrideApiId(tags) — a reserved tag used to pin a deterministic API id — supplying an id that is already in apiStore. The pre-existence check then fails with ConflictException, HTTP 409, matching AWS behavior for id conflicts.

Source

Thrown at src/main/java/io/github/hectorvent/floci/services/apigatewayv2/ApiGatewayV2Service.java:90

        if ("WEBSOCKET".equals(protocolType) && (routeSelectionExpression == null || routeSelectionExpression.isBlank())) {
            throw new AwsException("BadRequestException",
                    "RouteSelectionExpression is required for WEBSOCKET protocol", 400);
        }

        // Apply AWS defaults
        if (apiKeySelectionExpression == null) {
            apiKeySelectionExpression = "$request.header.x-api-key";
        }
        if ("HTTP".equals(protocolType) && routeSelectionExpression == null) {
            routeSelectionExpression = "${request.method} ${request.path}";
        }

        @SuppressWarnings("unchecked")
        Map<String, String> tags = (Map<String, String>) request.get("tags");
        String overrideId = ReservedTags.extractOverrideApiId(tags);
        String apiId = overrideId != null ? overrideId : shortId(10);
        if (apiStore.get(apiKey(region, apiId)).isPresent()) {
            throw new AwsException("ConflictException",
                    "API with id '" + apiId + "' already exists", 409);
        }

        Api api = new Api();
        api.setApiId(apiId);
        api.setName(name);
        api.setProtocolType(protocolType);
        api.setCreatedDate(System.currentTimeMillis());
        api.setRouteSelectionExpression(routeSelectionExpression);
        api.setDescription(description);
        api.setApiKeySelectionExpression(apiKeySelectionExpression);
        api.setDisableExecuteApiEndpoint(booleanValue(request.get("disableExecuteApiEndpoint")));

        if ("WEBSOCKET".equals(protocolType)) {
            api.setApiEndpoint(String.format("wss://%s.execute-api.%s.amazonaws.com", api.getApiId(), region));
        } else {
            api.setApiEndpoint(String.format("https://%s.execute-api.%s.amazonaws.com", api.getApiId(), region));
        }

View on GitHub (pinned to 62ff490619)

Solutions

  1. Delete the existing API first: aws apigatewayv2 delete-api --api-id <id>, then re-run create.
  2. If the test suite pins ids, add @AfterEach/@AfterAll cleanup (or unique ids per run) so re-runs start from a clean state.
  3. Only pass the reserved override tag on the initial creation; drop it on re-creation flows.
  4. If persistent storage is enabled and the id is stale, clean the persistence store or use a different override id.

Example fix

# before (re-run without cleanup)
aws apigatewayv2 create-api --name t --protocol-type HTTP \
  --tags floci/override-api-id=demo12345

# after
test -z "$(aws apigatewayv2 get-apis --query 'Items[?ApiId==`demo12345`].ApiId' --output text)" || \
  aws apigatewayv2 delete-api --api-id demo12345
aws apigatewayv2 create-api --name t --protocol-type HTTP \
  --tags floci/override-api-id=demo12345
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check the pinned id before creating
String overrideId = tags.get("floci/override-api-id"); // or your reserved tag key
if (overrideId != null) {
    try {
        v2Client.getApi(GetApiRequest.builder().apiId(overrideId).build());
        v2Client.deleteApi(DeleteApiRequest.builder().apiId(overrideId).build()); // or reuse it
    } catch (NotFoundException ok) { /* free to create */ }
}
v2Client.createApi(...);

Try / catch

catch (AwsException e) {
    if ("ConflictException".equals(e.getCode())) {
        // pinned id already live: delete-then-recreate, or skip creation entirely
        v2Client.deleteApi(DeleteApiRequest.builder().apiId(pinnedId).build());
        return v2Client.createApi(req); // single bounded retry
    }
    throw e;
}

Prevention

When it happens

Trigger: Creating an API twice with the same reserved override-id tag (e.g. in a test fixture or a re-run of a setup script that does not delete the prior API). Restoring or importing an API with a fixed id while one with that id still exists. Any createApi call whose tags carry the reserved override id of a live API.

Common situations: Integration-test suites that pin API ids via reserved tags for deterministic ARNs and run twice without cleanup. CI retries that re-apply the same creation template. Emulator state persisted to disk so previously created APIs survive restarts and collide with the next pinned create.

Related errors


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