{"record":{"id":"b30cfa7cd755364f","repo":"quarkusio/quarkus","slug":"failed-to-substitute-param","errorCode":null,"errorMessage":"Failed to substitute ${param}","messagePattern":"Failed to substitute (.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java","lineNumber":663,"sourceCode":"                return new DeferredArrayStoreParameter(param, expectedType) {\n\n                    @Override\n                    void doPrepare(MethodContext context) {\n                        serialized.prepare(context);\n                        super.doPrepare(context);\n                    }\n\n                    @Override\n                    ResultHandle createValue(MethodContext creator, MethodCreator method, ResultHandle array) {\n                        ResultHandle subInstance = method.newInstance(MethodDescriptor.ofConstructor(finalHolder.sub));\n                        return method.invokeInterfaceMethod(\n                                ofMethod(ObjectSubstitution.class, \"deserialize\", Object.class, Object.class), subInstance,\n                                creator.loadDeferred(serialized));\n                    }\n                };\n\n            } catch (Exception e) {\n                throw new RuntimeException(\"Failed to substitute \" + param, e);\n            }\n\n        } else if (param instanceof Optional) {\n            Optional val = (Optional) param;\n            if (val.isPresent()) {\n                DeferredParameter res = loadObjectInstance(val.get(), existing, Object.class, relaxedValidation);\n                return new DeferredArrayStoreParameter(param, expectedType) {\n\n                    @Override\n                    void doPrepare(MethodContext context) {\n                        res.prepare(context);\n                        super.doPrepare(context);\n                    }\n\n                    @Override\n                    ResultHandle createValue(MethodContext context, MethodCreator method, ResultHandle array) {\n                        // If the value is a proxy, it may be non-null at build time but become null\n                        // when we actually create the value during initialization;","sourceCodeStart":645,"sourceCodeEnd":681,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java#L645-L681","documentation":"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.","triggerScenarios":"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.","commonSituations":"Extension authors writing custom ObjectSubstitution classes; substitutions broken after refactoring the substituted type (renamed/removed fields); substitution class lacking a public no-arg constructor.","solutions":["Look at the 'Caused by' stack trace to find the exception inside your ObjectSubstitution.serialize()","Make sure the substitution class has a public no-arg constructor (it is instantiated reflectively)","Make serialize() handle null/edge-case fields defensively and only capture serializable state","Verify the substitution's serialize/deserialize are symmetrical so the generated bytecode can rebuild the object at runtime","Log the offending param's state before serialize() to spot which field is problematic"],"exampleFix":"// before\npublic Foo serialize(Foo t) { return new Foo(t.inner.name); } // NPE when inner==null\n// after\npublic Foo serialize(Foo t) {\n    return new Foo(t.inner == null ? null : t.inner.name);\n}","handlingStrategy":"validation","validationCode":"// before relying on a substitution\nObjectSubstitution<Foo, FooData> sub = new MyFooSubstitution();\nFooData data = sub.serialize(testFoo); // run at build step time to surface failures early\nassert sub.deserialize(data) != null : \"serialize/deserialize not symmetrical\";","typeGuard":"static <F, T> boolean isSubstitutable(Object param, Class<F> from) {\n    return from.isInstance(param);\n}","tryCatchPattern":"try {\n    recorder.accept(foo);\n} catch (RuntimeException e) {\n    throw new IllegalStateException(\"Substitution failed for \" + foo.getClass(), e.getCause());\n}","preventionTips":["Give substitution classes a public no-arg constructor","Make serialize() null-safe and symmetric with deserialize()","Unit-test substitutions at build time with representative objects","Re-test substitutions after refactoring the substituted type"],"tags":["quarkus","build-time","objectsubstitution","serialization"],"backgroundTag":"object-substitution-failed","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}