quarkusio/quarkus · error · RuntimeException
Cannot serialise field '${i.getName()}' on object '${param}'
Error message
Cannot serialise field '${i.getName()}' on object '${param}' as the property is read only What it means
Bytecode recording serializes an object by reading its getters and writing the values so they can be replayed via setters at runtime. If a getter's property has no matching setter AND a read-only backing field exists, the recorder cannot reconstruct the object and refuses to serialize it. If there is no backing field, the property is silently ignored.
Source
Thrown at core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java:1387
context.loadDeferred(e.getValue()));
}
}
@Override
public void prepare(MethodContext context) {
for (Map.Entry<DeferredParameter, DeferredParameter> e : def.entrySet()) {
e.getKey().prepare(context);
e.getValue().prepare(context);
}
}
});
}
} else if (!relaxedValidation && !i.getName().equals("class") && !relaxedOk
&& nonDefaultConstructorHolder == null) {
//check if there is actually a field with the name
try {
i.getReadMethod().getDeclaringClass().getDeclaredField(i.getName());
throw new RuntimeException("Cannot serialise field '" + i.getName() + "' on object '" + param
+ "' as the property is read only");
} catch (NoSuchFieldException e) {
//if there is no underlying field then we ignore the property
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
} else if (i.getReadMethod() != null && (i.getWriteMethod() != null || ctorParamIndex != null)) {
//normal javabean property
try {
handledProperties.add(i.getName());
Object propertyValue = i.read(param);
if (propertyValue == null && ctorParamIndex == null) {
//we just assume properties are null by default
//TODO: is this a valid assumption? Should we check this by creating an instance?View on GitHub (pinned to e1c734241f)
Solutions
- Add a setter matching the getter (setX with compatible type)
- Make it a writeable property or remove the getter
- Ensure there is no backing field so the property is ignored (e.g. rename the field) — not generally recommended
- Register a custom non-default constructor handler for the class instead of property-based serialization
Example fix
// before
public String getName() { return name; } // read-only
// after
public String getName() { return name; }
public void setName(String name) { this.name = name; } Defensive patterns
Strategy: validation
Validate before calling
for (Method g : clazz.getMethods()) {
if (g.getParameterCount() == 0 && g.getName().startsWith("get")) {
String prop = Introspector.decapitalize(g.getName().substring(3));
boolean hasSetter = Arrays.stream(clazz.getMethods()).anyMatch(s ->
s.getName().equals("set" + g.getName().substring(3)) && s.getParameterCount() == 1);
if (!hasSetter && hasDeclaredFieldOfType(clazz, prop)) {
throw new IllegalStateException("Read-only property with backing field: " + prop);
}
}
} Try / catch
try {
recorder.record(value);
} catch (RuntimeException e) {
if (String.valueOf(e.getMessage()).contains("as the property is read only")) {
throw new IllegalStateException("Add a setter for the read-only property on " + value.getClass(), e);
}
throw e;
} Prevention
- Ensure every bean getter of recorded classes has a matching setter
- Avoid recording immutable classes; use registered constructors instead
- Run a quick reflection audit on classes before adding them to @Record signatures
When it happens
Trigger: Passing an object to a @Record method whose class exposes a getter (e.g. isX()/getX()) with no corresponding setX(), but which has a final or private field named X; e.g. classes with derived getters over read-only fields.
Common situations: Immutable or builder-style value classes added to config records; classes where a getter exists but the setter was removed or renamed during refactoring.
Related errors
- Couldn't load object of type ${i.propertyType.getName()} for
- Failed to record call to method ${call.method}
- Failed to substitute ${param}
- Unable to serialize ${param} as the wrong number of paramete
- Unable to determine the recordable constructor to use for ${
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/f89370b1b3e94c4f.
Report an issue: GitHub.