apache/pulsar · error · IllegalArgumentException

Serializer type mismatch ${typeArg} vs ${serDeTypeArg}

Error message

Serializer type mismatch ${typeArg} vs ${serDeTypeArg}

What it means

Thrown by ValidatorUtils.validateSerde in the serialization (deser=false) branch when the SerDe's declared type parameter is not a supertype of the function's output type argument. For serialization, the SerDe must be able to accept the function's output type, i.e. serDeTypeArg.asErasure().isAssignableFrom(typeArg.asErasure()) must hold.

Source

Thrown at pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/ValidatorUtils.java:168

            throw new IllegalArgumentException(
                    String.format("The input serialization/deserialization class %s does not exist",
                            inputSerializer));
        }
        TypeDescription.Generic serDeTypeArg = serdeClass.getInterfaces().stream()
                .filter(i -> i.asErasure().isAssignableTo(SerDe.class))
                .findFirst()
                .map(i -> i.getTypeArguments().get(0))
                .orElseThrow(() -> new IllegalArgumentException(
                        String.format("%s does not implement %s", inputSerializer, SerDe.class.getName())));

        if (deser) {
            if (!serDeTypeArg.asErasure().isAssignableTo(typeArg.asErasure())) {
                throw new IllegalArgumentException("Serializer type mismatch " + typeArg.getActualName() + " vs "
                        + serDeTypeArg.getActualName());
            }
        } else {
            if (!serDeTypeArg.asErasure().isAssignableFrom(typeArg.asErasure())) {
                throw new IllegalArgumentException("Serializer type mismatch " + typeArg.getActualName() + " vs "
                        + serDeTypeArg.getActualName());
            }
        }
    }

    private static void validateSchemaType(TypeDefinition schema, TypeDefinition typeArg, TypePool typePool,
                                           boolean input) {

        TypeDescription.Generic schemaTypeArg = schema.getInterfaces().stream()
                .filter(i -> i.asErasure().isAssignableTo(Schema.class))
                .findFirst()
                .map(i -> i.getTypeArguments().get(0))
                .orElse(null);

        if (input) {
            if (!schemaTypeArg.asErasure().isAssignableTo(typeArg.asErasure())) {
                throw new IllegalArgumentException(
                        "Schema type mismatch " + typeArg.getActualName() + " vs " + schemaTypeArg.getActualName());

View on GitHub (pinned to 820761864e)

Solutions

  1. Set the function's output type argument to a type assignable to the SerDe's type parameter.
  2. Update the SerDe to implement SerDe<ActualOutputType> (or a supertype of the actual output).
  3. Point the output serializer config at a SerDe written for the current output type.

Example fix

// before
public class OutSerde implements SerDe<LegacyOut> { ... }
public class MyFunction implements Function<In, NewOut> { ... }
// after
public class OutSerde implements SerDe<NewOut> { ... }
Defensive patterns

Strategy: type-guard

Validate before calling

static boolean serdeMatchesOutput(String serdeClass, Class<?> outputType, ClassLoader cl) {
    try {
        for (Type t : Class.forName(serdeClass, false, cl).getGenericInterfaces()) {
            if (t instanceof ParameterizedType
                    && ((ParameterizedType) t).getRawType() == SerDe.class) {
                Type arg = ((ParameterizedType) t).getActualTypeArguments()[0];
                return arg instanceof Class<?> && outputType.isAssignableFrom((Class<?>) arg) == false
                        ? outputType.equals(arg) || ((Class<?>) arg).isAssignableFrom(outputType)
                        : true;
            }
        }
        return false;
    } catch (ClassNotFoundException e) { return false; }
}

Type guard

static boolean isSerDeAccepting(Class<?> serdeElementType, Class<?> outputType) {
    return serdeElementType.isAssignableFrom(outputType);
}

Try / catch

try {
    validateSerde(outputSerializer, typeArg, typePool, false);
} catch (IllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Serializer type mismatch")) {
        throw new IllegalStateException("Output SerDe " + outputSerializer
            + " cannot serialize function output type; update its generic parameter", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: validateSerde(inputSerializer, typeArg, typePool, false) where the SerDe implements SerDe<T> and typeArg is not assignable to T — e.g. the function outputs SubEvent but the output SerDe is SerDe<UnrelatedType>; or a SerDe typed to a sibling class that the output does not extend.

Common situations: Output type changed in a function refactor while the output SerDe config was left pointing at the old type's SerDe; using a base-class SerDe where the function emits an unrelated type; copy-pasting input SerDe settings into the output SerDe field.

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/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/140fda1ee9d5ec6f. Report an issue: GitHub.