{"record":{"id":"f89370b1b3e94c4f","repo":"quarkusio/quarkus","slug":"cannot-serialise-field-i-getname-on-object","errorCode":null,"errorMessage":"Cannot serialise field '${i.getName()}' on object '${param}' as the property is read only","messagePattern":"Cannot serialise field '(.+?)' on object '(.+?)' as the property is read only","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java","lineNumber":1387,"sourceCode":"                                                context.loadDeferred(e.getValue()));\n                                    }\n                                }\n\n                                @Override\n                                public void prepare(MethodContext context) {\n                                    for (Map.Entry<DeferredParameter, DeferredParameter> e : def.entrySet()) {\n                                        e.getKey().prepare(context);\n                                        e.getValue().prepare(context);\n                                    }\n                                }\n                            });\n                        }\n                    } else if (!relaxedValidation && !i.getName().equals(\"class\") && !relaxedOk\n                            && nonDefaultConstructorHolder == null) {\n                        //check if there is actually a field with the name\n                        try {\n                            i.getReadMethod().getDeclaringClass().getDeclaredField(i.getName());\n                            throw new RuntimeException(\"Cannot serialise field '\" + i.getName() + \"' on object '\" + param\n                                    + \"' as the property is read only\");\n                        } catch (NoSuchFieldException e) {\n                            //if there is no underlying field then we ignore the property\n                        }\n\n                    }\n\n                } catch (Exception e) {\n                    throw new RuntimeException(e);\n                }\n            } else if (i.getReadMethod() != null && (i.getWriteMethod() != null || ctorParamIndex != null)) {\n                //normal javabean property\n                try {\n                    handledProperties.add(i.getName());\n                    Object propertyValue = i.read(param);\n                    if (propertyValue == null && ctorParamIndex == null) {\n                        //we just assume properties are null by default\n                        //TODO: is this a valid assumption? Should we check this by creating an instance?","sourceCodeStart":1369,"sourceCodeEnd":1405,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java#L1369-L1405","documentation":"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.","triggerScenarios":"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.","commonSituations":"Immutable or builder-style value classes added to config records; classes where a getter exists but the setter was removed or renamed during refactoring.","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"],"exampleFix":"// before\npublic String getName() { return name; } // read-only\n// after\npublic String getName() { return name; }\npublic void setName(String name) { this.name = name; }","handlingStrategy":"validation","validationCode":"for (Method g : clazz.getMethods()) {\n    if (g.getParameterCount() == 0 && g.getName().startsWith(\"get\")) {\n        String prop = Introspector.decapitalize(g.getName().substring(3));\n        boolean hasSetter = Arrays.stream(clazz.getMethods()).anyMatch(s ->\n            s.getName().equals(\"set\" + g.getName().substring(3)) && s.getParameterCount() == 1);\n        if (!hasSetter && hasDeclaredFieldOfType(clazz, prop)) {\n            throw new IllegalStateException(\"Read-only property with backing field: \" + prop);\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    recorder.record(value);\n} catch (RuntimeException e) {\n    if (String.valueOf(e.getMessage()).contains(\"as the property is read only\")) {\n        throw new IllegalStateException(\"Add a setter for the read-only property on \" + value.getClass(), e);\n    }\n    throw e;\n}","preventionTips":["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"],"tags":["quarkus","bytecode-recording","serialization"],"backgroundTag":"read-only-property-serialization","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}