apache/pulsar · error · IllegalArgumentException
The message payload processor class %s does not implement th
Error message
The message payload processor class %s does not implement the desired constructor.
What it means
Thrown by ValidatorUtils.validateMessagePayloadProcessor when a configured MessagePayloadProcessor class does not declare the constructor required by its config. If MessagePayloadProcessorConfig.config is null or empty, the class must have a no-arg constructor; otherwise it must have a single-argument constructor taking a java.util.Map. The check runs at function-submission time via ByteBuddy's TypePool (bytecode inspection, no instantiation).
Source
Thrown at pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/ValidatorUtils.java:132
String.format("The message payload processor class %s does not exist", payloadProcessorClassName));
}
if (!payloadProcessorClass.asErasure().isAssignableTo(MessagePayloadProcessor.class)) {
throw new IllegalArgumentException(String.format("%s does not implement %s", payloadProcessorClassName,
MessagePayloadProcessor.class.getName()));
}
boolean hasConstructor;
if (conf.getConfig() == null || conf.getConfig().isEmpty()) {
hasConstructor = payloadProcessorClass.getDeclaredMethods().stream()
.anyMatch(method -> method.isConstructor() && method.getParameters().size() == 0);
} else {
hasConstructor = payloadProcessorClass.getDeclaredMethods().stream()
.anyMatch(method -> method.isConstructor() && method.getParameters().size() == 1
&& method.getParameters().get(0).getType().asErasure().represents(Map.class));
}
if (!hasConstructor) {
throw new IllegalArgumentException(
String.format("The message payload processor class %s does not implement the desired constructor.",
conf.getClassName()));
}
}
public static void validateSerde(String inputSerializer, TypeDefinition typeArg, TypePool typePool,
boolean deser) {
if (isEmpty(inputSerializer)) {
return;
}
if (inputSerializer.equals(DEFAULT_SERDE)) {
return;
}
TypeDescription serdeClass;
try {
serdeClass = typePool.describe(inputSerializer).resolve();
} catch (TypePool.Resolution.NoSuchTypeException e) {
throw new IllegalArgumentException(View on GitHub (pinned to 820761864e)
Solutions
- If you pass no config, add an explicit public no-arg constructor to the MessagePayloadProcessor class.
- If you pass config (conf.setConfig(...)), add a public constructor accepting a single java.util.Map<String, Object> (or Map<String, String>) parameter.
- Ensure the constructor is declared directly on the class, not inherited from a superclass.
- If the constructor exists but has a subtypes of Map parameter, change it to take exactly java.util.Map (raw or matching erasure) as its sole parameter.
Example fix
// before: processor with config supplied but no matching constructor
public class MyProcessor implements MessagePayloadProcessor {
public MyProcessor(String topic) { ... }
}
// after
public class MyProcessor implements MessagePayloadProcessor {
public MyProcessor() { ... }
public MyProcessor(Map<String, Object> config) { ... }
} Defensive patterns
Strategy: validation
Validate before calling
static boolean hasDesiredConstructor(String className, Map<String, String> config, ClassLoader cl) {
try {
Class<?> c = Class.forName(className, false, cl);
boolean wantsMap = config != null && !config.isEmpty();
for (Constructor<?> ctor : c.getDeclaredConstructors()) {
if (wantsMap) {
if (ctor.getParameterCount() == 1 && Map.class.isAssignableFrom(ctor.getParameterTypes()[0])) return true;
} else if (ctor.getParameterCount() == 0) {
return true;
}
}
return false;
} catch (ClassNotFoundException e) { return false; }
} Type guard
static boolean isMessagePayloadProcessorWithCtor(Class<?> c, boolean withConfig) {
return MessagePayloadProcessor.class.isAssignableFrom(c)
&& (withConfig
? java.util.Arrays.stream(c.getDeclaredConstructors()).anyMatch(k -> k.getParameterCount() == 1 && Map.class.isAssignableFrom(k.getParameterTypes()[0]))
: java.util.Arrays.stream(c.getDeclaredConstructors()).anyMatch(k -> k.getParameterCount() == 0));
} Try / catch
try {
functionConfigBuilder.validate();
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().contains("does not implement the desired constructor")) {
log.error("Payload processor {} needs a {} constructor", className, configEmpty ? "no-arg" : "Map", e);
}
throw e;
} Prevention
- Always declare an explicit public no-arg constructor AND a Map constructor on payload processor classes.
- Remember getDeclaredMethods() ignores inherited constructors — do not rely on a superclass's constructor.
- Keep the constructor style in sync with whether you set config on MessagePayloadProcessorConfig.
When it happens
Trigger: Calling validateMessagePayloadProcessor(conf, typePool) where conf.getClassName() resolves to a MessagePayloadProcessor subclass, the class exists and implements MessagePayloadProcessor, but: (a) conf.getConfig() is null/empty and the class has no declared no-arg constructor, or (b) conf.getConfig() is non-empty and the class lacks a constructor with exactly one Map parameter. Note the check inspects getDeclaredMethods(), so inherited constructors do not count.
Common situations: Declaring a payload processor that only has a constructor taking custom types (e.g. a String) while supplying config; supplying windowConfig config properties but the processor only defines a no-arg constructor; the processor relying on its superclass's constructor; Lombok or other annotation processors removing/altering the implicit default constructor.
Related errors
- Window length is not specified
- Window length for time and count are set! Please set one or
- Window length must be positive [${windowLengthCount}]
- Window length must be positive [${windowLengthDurationMs}]
- ${key} already exists in the dynamicConfigurationMap
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/169d1ca67003e4c3.
Report an issue: GitHub.