apache/druid · error · QueryDriver.RequestError

The class [%s] is not a Protobuf

Error message

The class [%s] is not a Protobuf

What it means

In getProtobufClass, Class.forName succeeds but the cast to Class<GeneratedMessage> fails (the loaded class is not a protobuf message type), throwing a ClassCastException that is wrapped in RequestError("The class [%s] is not a Protobuf"). The named class exists but is not a GeneratedMessage, so it cannot be used to encode result rows.

Source

Thrown at extensions-contrib/grpc-query/src/main/java/org/apache/druid/grpc/server/QueryDriver.java:764

    GrpcResultsAccumulator accumulator = new GrpcResultsAccumulator(writer);
    accumulator.push(result);
    return ByteString.copyFrom(out.toByteArray());
  }

  @SuppressWarnings("unchecked")
  private Class<GeneratedMessage> getProtobufClass(final QueryRequest request)
  {
    try {
      return (Class<GeneratedMessage>) Class.forName(request.getProtobufMessageName());
    }
    catch (ClassNotFoundException e) {
      throw new RequestError(
          "The Protobuf class [%s] is not known. Is your protobuf jar on the class path?",
          request.getProtobufMessageName()
      );
    }
    catch (ClassCastException e) {
      throw new RequestError(
          "The class [%s] is not a Protobuf",
          request.getProtobufMessageName()
      );
    }
  }
}

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Use the exact generated message class name (extends GeneratedMessageV3/GeneratedMessage), not the outer file class or Builder.
  2. Verify with Class.forName + GeneratedMessage.class.isAssignableFrom in a test before sending the request.
  3. Regenerate the protobuf classes with the protobuf Java compiler so the named class is a real message type.
  4. Check that the deployed jar is the compiled .proto output, not hand-written POJOs of the same names.

Example fix

// before
request.setProtobufMessageName("com.acme.ResultOuterClass");
// after
request.setProtobufMessageName("com.acme.ResultRow"); // the GeneratedMessage subclass
Defensive patterns

Strategy: type-guard

Validate before calling

// Confirm the named class is a protobuf message before use
Class<?> c = Class.forName(protobufMessageName);
if (!GeneratedMessage.class.isAssignableFrom(c)) {
  throw new IllegalArgumentException(protobufMessageName + " is not a protobuf message");
}

Type guard

boolean isGeneratedMessage(String name) {
  try {
    return GeneratedMessage.class.isAssignableFrom(Class.forName(name));
  } catch (Throwable t) {
    return false;
  }
}

Try / catch

try {
  return stub.query(req);
} catch (StatusRuntimeException e) {
  if (String.valueOf(e.getStatus().getDescription()).contains("is not a Protobuf")) {
    throw new IllegalArgumentException(req.getProtobufMessageName() + " must be a GeneratedMessage subclass", e);
  }
  throw e;
}

Prevention

When it happens

Trigger: Setting protobufMessageName to a valid FQCN of a non-message class — e.g. a protobuf Builder, an Enum wrapper class, a plain POJO, or the generated outer wrapper class (e.g. com.acme.FooOuterClass) instead of the concrete message type.

Common situations: Confusing the outer generated class name with the message class; naming a builder class by appending Builder; loading a class from a jar where the proto was compiled as POJOs (e.g. java_out without lite/full message generation).

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/9c46ec3bb59effd4. Report an issue: GitHub.