quarkusio/quarkus · error · RuntimeException

Cannot inject object of type ${param}

Error message

Cannot inject object of type ${param}

What it means

BytecodeRecorderImpl can satisfy recorder constructor parameters that are Class or simple known types only if an object of that exact type was previously loaded/recorded in the same recorder context (findLoaded). When a constructor parameter of raw type Class has no corresponding recorded instance, it throws this RuntimeException at augmentation time.

Source

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

        void preWrite(Map<Object, DeferredParameter> parameterMap) {
            if (injectCtor != null) {
                try {
                    java.lang.reflect.Type[] parameterTypes = injectCtor.getGenericParameterTypes();
                    Annotation[][] parameterAnnotations = injectCtor.getParameterAnnotations();
                    for (int i = 0; i < parameterTypes.length; i++) {
                        java.lang.reflect.Type param = parameterTypes[i];
                        var constantHolder = findConstantForParam(param);
                        if (constantHolder != null) {
                            deferredParameters.add(loadObjectInstance(constantHolder.value, parameterMap,
                                    constantHolder.type, Arrays.stream(parameterAnnotations[i])
                                            .anyMatch(s -> s.annotationType() == RelaxedValidation.class)));
                            continue;
                        }

                        if (param instanceof Class<?>) {
                            var result = findLoaded(null, (Class<?>) param);
                            if (result == null) {
                                throw new RuntimeException("Cannot inject object of type " + param);
                            }
                            deferredParameters.add(result);
                        } else if (param instanceof ParameterizedType paramType
                                && paramType.getRawType() == RuntimeValue.class) {
                            if (staticInit) {
                                deferredParameters.add(new DeferredParameter() {
                                    @Override
                                    ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) {
                                        return method.newInstance(MethodDescriptor.ofConstructor(RuntimeValue.class));
                                    }
                                });
                            } else {
                                var result = findLoaded(null, (Class<?>) paramType.getActualTypeArguments()[0]);
                                if (result == null) {
                                    throw new RuntimeException("Cannot inject object of type " + param);
                                }
                                deferredParameters.add(new DeferredParameter() {
                                    @Override

View on GitHub (pinned to e1c734241f)

Solutions

  1. Remove the Class-typed parameter and pass the value explicitly through a recorded method call instead
  2. Record/register the required object in the recorder context before the class is instantiated
  3. Change the constructor to accept only types the recorder can handle (primitives, String, Class with a recorded value, known config types)
  4. Store the class name as a String and resolve it at runtime

Example fix

// before
@Inject
MyRecorderConfig(Class<?> type) { this.type = type; }
// after
@Inject
MyRecorderConfig(String typeName) { this.typeName = typeName; } // resolve Class.forName at runtime
Defensive patterns

Strategy: validation

Validate before calling

static boolean ctorOnlyInjectableRawClassParams(Class<?> c) {
    return java.util.Arrays.stream(c.getDeclaredConstructors())
        .filter(k -> java.util.Arrays.stream(k.getAnnotations())
            .anyMatch(a -> a.annotationType().getSimpleName().equals("Inject")))
        .allMatch(k -> java.util.Arrays.stream(k.getParameterTypes())
            .noneMatch(p -> p == Class.class));
}

Try / catch

try {
    recorderMethod.invoke(...);
} catch (RuntimeException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Cannot inject object of type")) {
        throw new IllegalStateException("Register the value in the recorder before instantiation", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: An @Inject-annotated constructor of a recorded class declares a parameter typed as Class<?> (or another injectable raw type) and no value of that type has been recorded into the BytecodeRecorder before the object is created.

Common situations: Extension authors add a Class-typed parameter to a recorded object's constructor expecting it to be auto-wired; refactoring a recorder support class without registering the parameter value via recorder methods (e.g. recorder.registerParameters / returned recorded objects).

Related errors


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