{"record":{"id":"223d483f939341a1","repo":"pinpoint-apm/pinpoint","slug":"cannot-add-setter-to-final-field-settermethod","errorCode":null,"errorMessage":"Cannot add setter to final field. setterMethod: ${setterName}, fieldName: ${fieldName}","messagePattern":"Cannot add setter to final field\\. setterMethod: (.+?), fieldName: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"agent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/instrument/ASMClass.java","lineNumber":349,"sourceCode":"            final ASMFieldNodeAdapter fieldNode = this.classNode.getField(fieldName, null);\n            if (fieldNode == null) {\n                throw new IllegalArgumentException(\"Not found field. name=\" + fieldName);\n            }\n\n            final Type fieldType = setterDetails.getFieldType();\n            if (!fieldNode.getJavaType().equals(fieldType)) {\n                throw new IllegalArgumentException(\"Argument type of the setter is different with the field type. setterMethod: \" + fieldType + \", fieldType: \" + fieldNode.getJavaType());\n            }\n\n            if (fieldNode.isStatic()) {\n                throw new IllegalArgumentException(\"Cannot add setter to static fields. setterMethod: \" + setterDetails.getSetter().getName() + \", fieldName: \" + fieldName);\n            }\n\n            final int original = fieldNode.getAccess();\n            boolean finalRemoved = false;\n            if (fieldNode.isFinal()) {\n                if (!removeFinal) {\n                    throw new IllegalArgumentException(\"Cannot add setter to final field. setterMethod: \" + setterDetails.getSetter().getName() + \", fieldName: \" + fieldName);\n                } else {\n                    final int removed = original & ~Opcodes.ACC_FINAL;\n                    fieldNode.setAccess(removed);\n                    finalRemoved = true;\n                }\n            }\n\n            try {\n                boolean modified = finalRemoved;\n                modified |= this.classNode.addSetterMethod(setterDetails.getSetter().getName(), fieldNode);\n                modified |= this.classNode.addInterface(setterClass.getName());\n                if (modified) {\n                    setModified(true);\n                } else if (logger.isDebugEnabled()) {\n                    logger.debug(\"Skip addSetter, setter already declared. class={}, setter={}\", getName(), setterClass.getName());\n                }\n            } catch (Exception e) {\n                if (finalRemoved) {","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/pinpoint-apm/pinpoint/blob/744c3d3075e595656abb1ae331ad2c0e4c9eb996/agent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/instrument/ASMClass.java#L331-L367","documentation":"ASMClass.addSetter() refuses to add a setter to a field declared final, because writing it would violate the class's declared invariants. The caller may pass removeFinal=true to have the engine strip the ACC_FINAL flag before weaving; otherwise the transform is rejected with this IllegalArgumentException naming the setter method and field.","triggerScenarios":"Calling the addSetter API against a final field while removeFinal is false (default). fieldNode.isFinal() is true in the target class bytecode.","commonSituations":"Targeting immutable/value fields or constants accidentally; library versions where a field was made final; plugin authors unaware that removeFinal must be explicitly enabled.","solutions":["Pass removeFinal=true to addSetter if mutating the final field is intentional","Choose a non-final field, or drop the setter approach and use an interceptor on accessing methods","Verify with javap whether the field is final in the exact target library version"],"exampleFix":"// before\nclazz.addSetter(setterClass, \"url\"); // url is final\n// after\nclazz.addSetter(setterClass, \"url\", true); // removeFinal = true","handlingStrategy":"validation","validationCode":"Field f = targetClass.getDeclaredField(fieldName);\nif (Modifier.isFinal(f.getModifiers())) {\n    // decide: pass removeFinal=true or pick another field\n}","typeGuard":"boolean isMutableField(Class<?> c, String name) {\n    try { int m = c.getDeclaredField(name).getModifiers(); return !Modifier.isFinal(m) && !Modifier.isStatic(m); } catch (NoSuchFieldException e) { return false; }\n}","tryCatchPattern":null,"preventionTips":["Avoid targeting final fields in plugins","Only enable removeFinal when mutation is truly required and safe","Re-check field modifiers on every library upgrade"],"tags":["java","bytecode-instrumentation","final-field"],"backgroundTag":"unsupported-operation","analyzedSha":"744c3d3075e595656abb1ae331ad2c0e4c9eb996","analyzedAt":"2026-09-07T18:48:45.289Z","contentChangedAt":"2026-09-07T18:48:45.289Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}