{"record":{"id":"3357fd7cc85c3741","repo":"floci-io/floci","slug":"conflictexception-3357fd","errorCode":"ConflictException","errorMessage":"API with id '{apiId}' already exists","messagePattern":"API with id '(.+?)' already exists","errorType":"error_code","errorClass":"AwsException","httpStatus":409,"severity":"error","filePath":"src/main/java/io/github/hectorvent/floci/services/apigatewayv2/ApiGatewayV2Service.java","lineNumber":90,"sourceCode":"        if (\"WEBSOCKET\".equals(protocolType) && (routeSelectionExpression == null || routeSelectionExpression.isBlank())) {\n            throw new AwsException(\"BadRequestException\",\n                    \"RouteSelectionExpression is required for WEBSOCKET protocol\", 400);\n        }\n\n        // Apply AWS defaults\n        if (apiKeySelectionExpression == null) {\n            apiKeySelectionExpression = \"$request.header.x-api-key\";\n        }\n        if (\"HTTP\".equals(protocolType) && routeSelectionExpression == null) {\n            routeSelectionExpression = \"${request.method} ${request.path}\";\n        }\n\n        @SuppressWarnings(\"unchecked\")\n        Map<String, String> tags = (Map<String, String>) request.get(\"tags\");\n        String overrideId = ReservedTags.extractOverrideApiId(tags);\n        String apiId = overrideId != null ? overrideId : shortId(10);\n        if (apiStore.get(apiKey(region, apiId)).isPresent()) {\n            throw new AwsException(\"ConflictException\",\n                    \"API with id '\" + apiId + \"' already exists\", 409);\n        }\n\n        Api api = new Api();\n        api.setApiId(apiId);\n        api.setName(name);\n        api.setProtocolType(protocolType);\n        api.setCreatedDate(System.currentTimeMillis());\n        api.setRouteSelectionExpression(routeSelectionExpression);\n        api.setDescription(description);\n        api.setApiKeySelectionExpression(apiKeySelectionExpression);\n        api.setDisableExecuteApiEndpoint(booleanValue(request.get(\"disableExecuteApiEndpoint\")));\n\n        if (\"WEBSOCKET\".equals(protocolType)) {\n            api.setApiEndpoint(String.format(\"wss://%s.execute-api.%s.amazonaws.com\", api.getApiId(), region));\n        } else {\n            api.setApiEndpoint(String.format(\"https://%s.execute-api.%s.amazonaws.com\", api.getApiId(), region));\n        }","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/floci-io/floci/blob/62ff490619e7bd3554597c28c704081b4c15add5/src/main/java/io/github/hectorvent/floci/services/apigatewayv2/ApiGatewayV2Service.java#L72-L108","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Delete the existing API first: aws apigatewayv2 delete-api --api-id <id>, then re-run create.","If the test suite pins ids, add @AfterEach/@AfterAll cleanup (or unique ids per run) so re-runs start from a clean state.","Only pass the reserved override tag on the initial creation; drop it on re-creation flows.","If persistent storage is enabled and the id is stale, clean the persistence store or use a different override id."],"exampleFix":"# before (re-run without cleanup)\naws apigatewayv2 create-api --name t --protocol-type HTTP \\\n  --tags floci/override-api-id=demo12345\n\n# after\ntest -z \"$(aws apigatewayv2 get-apis --query 'Items[?ApiId==`demo12345`].ApiId' --output text)\" || \\\n  aws apigatewayv2 delete-api --api-id demo12345\naws apigatewayv2 create-api --name t --protocol-type HTTP \\\n  --tags floci/override-api-id=demo12345","handlingStrategy":"try-catch","validationCode":"// Pre-check the pinned id before creating\nString overrideId = tags.get(\"floci/override-api-id\"); // or your reserved tag key\nif (overrideId != null) {\n    try {\n        v2Client.getApi(GetApiRequest.builder().apiId(overrideId).build());\n        v2Client.deleteApi(DeleteApiRequest.builder().apiId(overrideId).build()); // or reuse it\n    } catch (NotFoundException ok) { /* free to create */ }\n}\nv2Client.createApi(...);","typeGuard":null,"tryCatchPattern":"catch (AwsException e) {\n    if (\"ConflictException\".equals(e.getCode())) {\n        // pinned id already live: delete-then-recreate, or skip creation entirely\n        v2Client.deleteApi(DeleteApiRequest.builder().apiId(pinnedId).build());\n        return v2Client.createApi(req); // single bounded retry\n    }\n    throw e;\n}","preventionTips":["Make test fixtures delete pinned-id APIs in teardown (@AfterEach).","Only pass the override tag on first creation; store the ApiId for later runs.","With persistent storage, reset or clean state between CI runs that pin ids."],"tags":["apigatewayv2","conflict","reserved-tags","create-api"],"backgroundTag":null,"analyzedSha":"62ff490619e7bd3554597c28c704081b4c15add5","analyzedAt":"2026-08-14T14:25:23.764Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}