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
- Annotate exactly one constructor of the class with jakarta.inject.Inject (or javax.inject.Inject)
- Reduce the class to a single constructor so the recorder can pick it unambiguously
- Provide a no-arg constructor plus setters, or refactor the class into a simple POJO with default constructor
- 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
- Give recorded classes exactly one constructor, annotated @Inject
- Never add convenience constructors to recorder-support/config classes
- Review any constructor addition in classes returned from @Recorder methods
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
- Cannot inject object of type ${param}
- Failed to prepare injector constructor ${injectCtor} of byte
- Unsupported value: ${value}
- Cannot create new DeferredArrayStoreParameter after array ha
- Failed to injector constructor ${injectCtor} of bytecode rec
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/f7bd25299beaf8a5.
Report an issue: GitHub.