floci-io/floci · error · AwsException
BadRequestException
BadRequestException
Error message
RouteSelectionExpression is required for WEBSOCKET protocol
What it means
Thrown by ApiGatewayV2Service.createApi() when protocolType is WEBSOCKET and routeSelectionExpression is absent or blank. AWS requires WebSocket APIs to declare how a frame's JSON payload selects a route (commonly '$request.body.action'), because unlike HTTP APIs there is no method/path to derive the route from. HTTP APIs get a default expression ('${request.method} ${request.path}') and never hit this check; WebSocket APIs have no safe default, so the request is rejected with HTTP 400 before any id is allocated.
Source
Thrown at src/main/java/io/github/hectorvent/floci/services/apigatewayv2/ApiGatewayV2Service.java:73
new TypeReference<>() {});
this.modelStore = storageFactory.create("apigatewayv2", "apigatewayv2-models.json",
new TypeReference<>() {});
this.vpcLinkStore = storageFactory.create("apigatewayv2", "apigatewayv2-vpclinks.json",
new TypeReference<>() {});
this.regionResolver = regionResolver;
}
// ──────────────────────────── API CRUD ────────────────────────────
public Api createApi(String region, Map<String, Object> request) {
String name = (String) request.get("name");
String protocolType = (String) request.getOrDefault("protocolType", "HTTP");
String routeSelectionExpression = (String) request.get("routeSelectionExpression");
String description = (String) request.get("description");
String apiKeySelectionExpression = (String) request.get("apiKeySelectionExpression");
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);View on GitHub (pinned to 62ff490619)
Solutions
- Add --route-selection-expression '$request.body.action' (or whatever field routes your frames) to the create-api call.
- In IaC, ensure route_selection_expression is set on every AWS::ApiGatewayV2::Api whose ProtocolType is WEBSOCKET.
- Verify the JSON key casing is exactly 'routeSelectionExpression'.
- Keep HTTP APIs unchanged — they receive the default '${request.method} ${request.path}' automatically.
Example fix
# before aws apigatewayv2 create-api --name chat --protocol-type WEBSOCKET # after aws apigatewayv2 create-api --name chat --protocol-type WEBSOCKET \ --route-selection-expression '$request.body.action'
Defensive patterns
Strategy: validation
Validate before calling
// Validate before createApi
String protocolType = request.getOrDefault("protocolType", "HTTP").toString();
String rse = (String) request.get("routeSelectionExpression");
if ("WEBSOCKET".equals(protocolType) && (rse == null || rse.isBlank())) {
request.put("routeSelectionExpression", "$request.body.action"); // or fail fast
}
apiGatewayV2.createApi(region, request); Type guard
boolean canCreateApi(Map<String, Object> req) {
String pt = (String) req.getOrDefault("protocolType", "HTTP");
String rse = (String) req.get("routeSelectionExpression");
return !"WEBSOCKET".equals(pt) || (rse != null && !rse.isBlank());
} Prevention
- In IaC, make route_selection_expression a required attribute when ProtocolType is WEBSOCKET.
- Add a schema check in the deploy pipeline that rejects WEBSOCKET APIs without the expression.
- Standardize on '$request.body.action' unless routing on a different payload field.
When it happens
Trigger: aws apigatewayv2 create-api --name ws-api --protocol-type WEBSOCKET with no --route-selection-expression flag. Or passing the expression under a misspelled key in the JSON body (routeSelectionExpression vs routeselectionexpression) so the map lookup returns null. Passing whitespace-only strings also triggers it because of the isBlank() check.
Common situations: Copy-pasting an HTTP API creation command and only changing protocolType. Terraform/CDK templates that conditionally set route_selection_expression and skip it for WebSocket resources. Assuming an emulator will default the field the way it defaults apiKeySelectionExpression.
Understand the failure class
Background: BadRequestException (HTTP 400) — NestJS 'Bad Request' Errors: Why They Fire and How to Fix Them — this error's family across 4 libraries.
Related errors
- ConflictException
- TLS enabled but no certificate provided and self-signed gene
- ValidationException
- ValidationException
- floci.storage.efs root-permissions must be 3-4 octal digits
AI-assisted analysis of floci-io/floci@62ff490619 (2026-08-14).
Data as JSON: /api/errors/d34c8f9708854a4f.
Report an issue: GitHub.