quarkusio/quarkus · error · RuntimeException

Could not determine constructor for

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.

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.

Appendix: 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)