quarkusio/quarkus · error · RuntimeException

Failed to substitute ${param}

Error message

Failed to substitute ${param}

What it means

When a recorder parameter's type has an ObjectSubstitution registered, BytecodeRecorderImpl instantiates the substitution and calls serialize(); any exception in that path is wrapped as 'Failed to substitute <param>'. The root cause is inside the custom ObjectSubstitution implementation (or reflection on it), not in Quarkus.

Source

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

                return new DeferredArrayStoreParameter(param, expectedType) {

                    @Override
                    void doPrepare(MethodContext context) {
                        serialized.prepare(context);
                        super.doPrepare(context);
                    }

                    @Override
                    ResultHandle createValue(MethodContext creator, MethodCreator method, ResultHandle array) {
                        ResultHandle subInstance = method.newInstance(MethodDescriptor.ofConstructor(finalHolder.sub));
                        return method.invokeInterfaceMethod(
                                ofMethod(ObjectSubstitution.class, "deserialize", Object.class, Object.class), subInstance,
                                creator.loadDeferred(serialized));
                    }
                };

            } catch (Exception e) {
                throw new RuntimeException("Failed to substitute " + param, e);
            }

        } else if (param instanceof Optional) {
            Optional val = (Optional) param;
            if (val.isPresent()) {
                DeferredParameter res = loadObjectInstance(val.get(), existing, Object.class, relaxedValidation);
                return new DeferredArrayStoreParameter(param, expectedType) {

                    @Override
                    void doPrepare(MethodContext context) {
                        res.prepare(context);
                        super.doPrepare(context);
                    }

                    @Override
                    ResultHandle createValue(MethodContext context, MethodCreator method, ResultHandle array) {
                        // If the value is a proxy, it may be non-null at build time but become null
                        // when we actually create the value during initialization;

View on GitHub (pinned to e1c734241f)

Solutions

  1. Look at the 'Caused by' stack trace to find the exception inside your ObjectSubstitution.serialize()
  2. Make sure the substitution class has a public no-arg constructor (it is instantiated reflectively)
  3. Make serialize() handle null/edge-case fields defensively and only capture serializable state
  4. Verify the substitution's serialize/deserialize are symmetrical so the generated bytecode can rebuild the object at runtime
  5. Log the offending param's state before serialize() to spot which field is problematic

Example fix

// before
public Foo serialize(Foo t) { return new Foo(t.inner.name); } // NPE when inner==null
// after
public Foo serialize(Foo t) {
    return new Foo(t.inner == null ? null : t.inner.name);
}
Defensive patterns

Strategy: validation

Validate before calling

// before relying on a substitution
ObjectSubstitution<Foo, FooData> sub = new MyFooSubstitution();
FooData data = sub.serialize(testFoo); // run at build step time to surface failures early
assert sub.deserialize(data) != null : "serialize/deserialize not symmetrical";

Type guard

static <F, T> boolean isSubstitutable(Object param, Class<F> from) {
    return from.isInstance(param);
}

Try / catch

try {
    recorder.accept(foo);
} catch (RuntimeException e) {
    throw new IllegalStateException("Substitution failed for " + foo.getClass(), e.getCause());
}

Prevention

When it happens

Trigger: A recorder method receives an object whose class (or declared expectedType) has a registered ObjectSubstitution, and substitution.serialize(param) throws — e.g. NullPointerException on a null field, missing no-arg constructor on the substitution class, or a checked exception during serialization.

Common situations: Extension authors writing custom ObjectSubstitution classes; substitutions broken after refactoring the substituted type (renamed/removed fields); substitution class lacking a public no-arg constructor.

Related errors


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