quarkusio/quarkus · error · RuntimeException

Unable to serialize as the wrong number of parameters were…

Error message

Unable to serialize ${param} as the wrong number of parameters were generated for ${nonDefaultConstructorHolder.constructor}

What it means

Bytecode recording of a non-default-constructor object requires a registered paramGenerator that produces exactly the constructor's arguments at record time. When the generator returns a list whose size differs from the constructor's parameter count, serialization is aborted. This is a bug in the extension that registered the constructor (via io.quarkus.deployment.recording.RecordableConstructor or ConstructorParameterCustomizer-style registration), not typically a user config mistake.

Solutions

  1. Update the registered paramGenerator to return exactly one value per constructor parameter, in order
  2. Recompile against the current constructor signature (parameter count/types may have changed in a newer Quarkus or extension version)
  3. Add an assertion/unit test that params.size() == constructor.getParameterCount() for your generated class

Example fix

// before
holder.paramGenerator = obj -> List.of(obj.getName());
// after (ctor is Foo(String name, int size))
holder.paramGenerator = obj -> List.of(obj.getName(), obj.getSize());
Defensive patterns

Strategy: validation

Validate before calling

List<Object> params = holder.paramGenerator.apply(instance);
if (params.size() != holder.constructor.getParameterCount()) {
    throw new IllegalStateException("paramGenerator/constructor arity mismatch for "
        + holder.constructor.getDeclaringClass());
}

Type guard

boolean arityOk(Function<Object,List<Object>> gen, Constructor<?> c, Object o) {
    return gen.apply(o).size() == c.getParameterCount();
}

Try / catch

try {
    recorder.record(instance);
} catch (RuntimeException e) {
    if (e.getMessage() != null && e.getMessage().contains("wrong number of parameters")) {
        throw new IllegalStateException("Fix registered paramGenerator for " + instance.getClass(), e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Registering a non-default constructor for a class where the supplied paramGenerator function (e.g. via RecordingAnnotations/ConstructorParameterCustomizer or a returned non-serializable object with a RecordableConstructor) returns fewer or more values than constructor.getParameterCount(); commonly after the class gains a new constructor parameter while the generator was not updated.

Common situations: Extension code evolved: a Quarkus upgrade changed a config-class constructor signature while custom param generation logic in the extension was left stale; hand-written paramGenerator with a hardcoded list of values.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/1ed37a000977083b. Report an issue: GitHub.

Appendix: source

Thrown at core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java:1223

                        key.prepare(context);
                        val.prepare(context);
                    }
                });
            }
            relaxedOk = true;
        }

        //check how the object is constructed
        NonDefaultConstructorHolder nonDefaultConstructorHolder = null;
        DeferredParameter[] nonDefaultConstructorHandles = null;
        //used to resolve the parameter position for @RecordableConstructor
        Map<String, Integer> constructorParamNameMap = new HashMap<>();

        if (nonDefaultConstructors.containsKey(param.getClass())) {
            nonDefaultConstructorHolder = nonDefaultConstructors.get(param.getClass());
            List<Object> params = nonDefaultConstructorHolder.paramGenerator.apply(param);
            if (params.size() != nonDefaultConstructorHolder.constructor.getParameterCount()) {
                throw new RuntimeException("Unable to serialize " + param
                        + " as the wrong number of parameters were generated for "
                        + nonDefaultConstructorHolder.constructor);
            }
            int count = 0;
            nonDefaultConstructorHandles = new DeferredParameter[params.size()];
            Class<?>[] parameterTypes = nonDefaultConstructorHolder.constructor.getParameterTypes();
            for (int i = 0; i < params.size(); i++) {
                Object obj = params.get(i);
                nonDefaultConstructorHandles[i] = loadObjectInstance(obj, existing,
                        parameterTypes[count++], relaxedValidation);
            }
            extractConstructorParameterNames(nonDefaultConstructorHolder.constructor, constructorParamNameMap);
        } else if (classesToUseRecordableConstructor.contains(param.getClass())) {
            Constructor<?> current = null;
            int count = 0;
            for (var c : param.getClass().getConstructors()) {
                if (current == null || current.getParameterCount() < c.getParameterCount()) {
                    current = c;

View on GitHub (pinned to e1c734241f)