quarkusio/quarkus · error · RuntimeException

Could not determine constructor for ${theClass} add @Inject

Error message

Could not determine constructor for ${theClass} add @Inject to a constructor

What it means

BytecodeRecorderImpl serializes recorded build-time objects into generated bytecode so they can be re-created at runtime. When it needs to instantiate a recorded class it must pick a constructor; if the class has multiple constructors and none is annotated with @Inject, the recorder cannot decide which to use and throws this RuntimeException at augmentation time.

Source

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

        NewRecorder(Class<?> theClass) {
            super(theClass.getName());
            this.theClass = theClass;
            Constructor<?> injectCtor = null;
            Constructor<?>[] ctors = theClass.getDeclaredConstructors();
            if (ctors.length == 1) {
                injectCtor = ctors[0];
            } else {
                for (var i : ctors) {
                    if (i.isAnnotationPresent(Inject.class)) {
                        if (injectCtor == null) {
                            injectCtor = i;
                        } else {
                            throw new RuntimeException("Multiple @Inject constructors on " + theClass);
                        }
                    }
                }
                if (injectCtor == null) {
                    throw new RuntimeException(
                            "Could not determine constructor for " + theClass + " add @Inject to a constructor");
                }
            }
            this.injectCtor = injectCtor;
        }

        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)));

View on GitHub (pinned to e1c734241f)

Solutions

  1. Annotate exactly one constructor of the class with jakarta.inject.Inject (or javax.inject.Inject)
  2. Reduce the class to a single constructor so the recorder can pick it unambiguously
  3. Provide a no-arg constructor plus setters, or refactor the class into a simple POJO with default constructor
  4. If the class is not yours, wrap it in a small holder class with a single @Inject constructor

Example fix

// before
class MyRecorderConfig {
    MyRecorderConfig(String name) { ... }
    MyRecorderConfig(String name, int size) { ... }
}
// after
class MyRecorderConfig {
    @Inject
    MyRecorderConfig(String name) { ... }
    // removed or delegating second constructor
}
Defensive patterns

Strategy: validation

Validate before calling

static boolean hasUnambiguousCtor(Class<?> c) {
    long injects = java.util.Arrays.stream(c.getDeclaredConstructors())
        .filter(k -> java.util.Arrays.stream(k.getAnnotations())
            .anyMatch(a -> a.annotationType().getSimpleName().equals("Inject"))).count();
    return c.getDeclaredConstructors().length == 1 || injects == 1;
}

Type guard

static boolean hasInjectCtor(Class<?> c) {
    return java.util.Arrays.stream(c.getDeclaredConstructors())
        .anyMatch(k -> java.util.Arrays.stream(k.getAnnotations())
            .anyMatch(a -> a.annotationType().getSimpleName().equals("Inject")));
}

Try / catch

try {
    recorder.returnValue(recorded);
} catch (RuntimeException e) {
    if (e.getMessage() != null && e.getMessage().contains("Could not determine constructor")) {
        throw new IllegalStateException("Add @Inject to exactly one constructor of " + recorded.getClass(), e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Recording (via BytecodeRecorderImpl or @Recorder proxies / runtime initialization) a class that has more than one constructor and neither a unique no-arg constructor nor an @Inject-annotated constructor. Typically hit when returning custom objects from @Recorder methods or using RuntimeValue/recorded config objects whose class lacks @Inject.

Common situations: Extension authors add a convenience overloaded constructor to a recorded config or recorder-support class and forget to mark the intended one @Inject; upgrading Quarkus changes how classes are recorded; third-party classes with multiple constructors are used inside recorded objects.

Related errors


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