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

  1. Return HTTP 400 to the client and require a non-empty JSON body on the chat completions endpoint
  2. Read the request body via ServerWebExchange/ServerHttpRequest properly before adapting
  3. Check that no intermediate plugin consumed or discarded the request body
  4. 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

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


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)