apache/shenyu · error · ShenyuException
Request body must not be empty
Error message
Request body must not be empty
What it means
OpenAiProtocolAdapter.toChatCompletionRequest parses an incoming OpenAI-compatible chat completion request body. The very first check rejects a null or empty string body with ShenyuException('Request body must not be empty'), since there is no JSON to model a request from.
Solutions
- Return HTTP 400 to the client and require a non-empty JSON body on the chat completions endpoint
- Read the request body via ServerWebExchange/ServerHttpRequest properly before adapting
- Check that no intermediate plugin consumed or discarded the request body
- Add an explicit empty-body check/handler in the AI plugin before calling the adapter
Example fix
// before
String body = ""; // never populated
OpenAiProtocolAdapter.toChatCompletionRequest(body, stream, config);
// after
if (body == null || body.isBlank()) { return badRequest("body required"); }
ChatCompletionRequest req = OpenAiProtocolAdapter.toChatCompletionRequest(body, stream, config); Defensive patterns
Strategy: validation
Validate before calling
if (requestBody == null || requestBody.isBlank()) {
return ServerResponse.badRequest("request body required");
} Try / catch
try {
ChatCompletionRequest req = OpenAiProtocolAdapter.toChatCompletionRequest(body, stream, config);
} catch (ShenyuException e) {
if (e.getMessage().contains("must not be empty")) {
return ResponseEntity.badRequest().body("request body must not be empty");
}
throw e;
} Prevention
- Enforce a non-empty JSON body on AI proxy routes
- Buffer/read the body exactly once before adapting
- Check no upstream plugin consumed the body
- Return 400 with a clear message to clients sending empty bodies
When it happens
Trigger: Calling toChatCompletionRequest(requestBody, stream, fallbackConfig) with null or "" as the body — typically when the gateway plugin forwarded a request whose body was never read or was stripped.
Common situations: Client sent an empty POST body to the AI proxy route, body already consumed upstream so it reads as empty, streaming requests where the raw body was not buffered.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- Invalid request body: expected a JSON object
- Tool execution timeout or error
- Invalid input JSON format
- Invalid JSON format
- Invalid URI construction
AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12).
Data as JSON: /api/errors/cafecdf5706b0b3c.
Report an issue: GitHub.
Appendix: source
Thrown at shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/protocol/OpenAiProtocolAdapter.java:119
public static ChatCompletionRequest toChatCompletionRequest(final String requestBody, final boolean stream) {
return toChatCompletionRequest(requestBody, stream, null);
}
/**
* Parse raw request body directly into ChatCompletionRequest, preserving modeled fields.
* When fallbackConfig is provided, its non-null fields (model, temperature, maxTokens)
* override the client request values, matching the original ChatModel-based fallback behavior
* where the fallback ChatModel's config takes precedence.
*
* @param requestBody the raw JSON request body in OpenAI Chat Completions format
* @param stream whether this is a streaming request
* @param fallbackConfig the fallback config whose non-null fields override client values
* @return a ChatCompletionRequest with modeled fields preserved
*/
public static ChatCompletionRequest toChatCompletionRequest(final String requestBody,
final boolean stream, final AiCommonConfig fallbackConfig) {
if (Objects.isNull(requestBody) || requestBody.isEmpty()) {
throw new ShenyuException("Request body must not be empty");
}
final JsonNode root = parseStrict(requestBody);
if (Objects.isNull(root) || !root.isObject()) {
throw new ShenyuException("Invalid request body: expected a JSON object");
}
final ObjectNode mutableRoot = (ObjectNode) root;
if (Objects.nonNull(fallbackConfig)) {
if (Objects.nonNull(fallbackConfig.getModel()) && !fallbackConfig.getModel().isEmpty()) {
mutableRoot.put(FIELD_MODEL, fallbackConfig.getModel());
}
if (Objects.nonNull(fallbackConfig.getTemperature())) {
mutableRoot.put(FIELD_TEMPERATURE, fallbackConfig.getTemperature());
}
if (Objects.nonNull(fallbackConfig.getMaxTokens())) {
mutableRoot.put(FIELD_MAX_TOKENS, fallbackConfig.getMaxTokens());
mutableRoot.put(FIELD_MAX_COMPLETION_TOKENS, fallbackConfig.getMaxTokens());
}View on GitHub (pinned to 567142e072)