alibaba/Sentinel · error · IllegalArgumentException

Bad class metadata

Error message

Bad class metadata

What it means

When the netty-http command center writes a response, pickEncoder selects an Encoder from the codec registry for the response body's class. If the response body class is null (handler returned a null-typed result), it throws IllegalArgumentException("Bad class metadata") as a fail-fast guard. The response body class normally comes from CommandResponse.getBody() via getBodyClass reflection.

Source

Thrown at sentinel-transport/sentinel-transport-netty-http/src/main/java/com/alibaba/csp/sentinel/transport/command/netty/HttpServerHandler.java:106

    }

    private void handleRequest(CommandRequest request, ChannelHandlerContext ctx, boolean keepAlive)
        throws Exception {
        String commandName = HttpCommandUtils.getTarget(request);
        // Find the matching command handler.
        CommandHandler<?> commandHandler = getHandler(commandName);
        if (commandHandler != null) {
            CommandResponse<?> response = commandHandler.handle(request);
            writeResponse(response, ctx, keepAlive);
        } else {
            // No matching command handler.
            writeErrorResponse(BAD_REQUEST.code(), String.format("Unknown command \"%s\"", commandName), ctx);
        }
    }

    private Encoder<?> pickEncoder(Class<?> clazz) {
        if (clazz == null) {
            throw new IllegalArgumentException("Bad class metadata");
        }
        for (Encoder<?> encoder : codecRegistry.getEncoderList()) {
            if (encoder.canEncode(clazz)) {
                return encoder;
            }
        }
        return null;
    }

    private void writeErrorResponse(int statusCode, String message, ChannelHandlerContext ctx) {
        FullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
            HttpResponseStatus.valueOf(statusCode),
            Unpooled.copiedBuffer(message, Charset.forName(SentinelConfig.charset())));

        httpResponse.headers().set("Content-Type", "text/plain; charset=" + SentinelConfig.charset());
        ctx.write(httpResponse);

        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);

View on GitHub (pinned to a3f40ba8e9)

Solutions

  1. Always return a non-null body from custom CommandHandlers, e.g. CommandResponse.ofSuccess("") or a JSON error string
  2. Return CommandResponse.ofFailure with a String message instead of null on error paths
  3. Register a fallback Encoder whose canEncode accepts String and route null bodies to an empty-string response before writeResponse

Example fix

// before
public CommandResponse<String> handle(CommandRequest req) {
    String rules = loadRules(req);
    return CommandResponse.ofSuccess(rules); // null when nothing loaded
}

// after
public CommandResponse<String> handle(CommandRequest req) {
    String rules = loadRules(req);
    return CommandResponse.ofSuccess(rules == null ? "[]" : rules);
}
Defensive patterns

Strategy: validation

Validate before calling

Object body = response.getBody();
if (body == null) {
    response = CommandResponse.ofSuccess(""); // ensure a class exists for encoder pick
}

Prevention

When it happens

Trigger: A CommandHandler returning CommandResponse.ofSuccess(null) (or ofFailure with null error body) so the response body is null and its class cannot be determined when HttpServerHandler.writeResponse picks the encoder.

Common situations: Custom CommandHandler implementations that return null bodies for not-found/edge cases; upgrading Sentinel where response encoding became class-based; handlers modeled on ones that always return a String but with a branch returning null.

Related errors


AI-assisted analysis of alibaba/Sentinel@a3f40ba8e9 (2026-08-14). Data as JSON: /api/errors/44f7291dadc4121d. Report an issue: GitHub.