{"record":{"id":"b55bc00dd8f6a668","repo":"pinpoint-apm/pinpoint","slug":"after-must-return-java-lang-object-targetint","errorCode":null,"errorMessage":"${after} must return java.lang.Object. ${targetInterceptorClazz.getName()}","messagePattern":"(.+?) must return java\\.lang\\.Object\\. (.+?)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"agent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/instrument/interceptor/InterceptorDefinitionFactory.java","lineNumber":194,"sourceCode":"        }\n\n        private InterceptorDefinition createInterceptorDefinition(Class<? extends Interceptor> targetInterceptorClazz) {\n\n            final Method beforeMethod = searchMethod(targetInterceptorClazz, before, beforeParamList);\n            if (beforeMethod == null) {\n                throw new RuntimeException(before + \" method not found. \" + Arrays.toString(beforeParamList));\n            }\n            final boolean beforeIgnoreMethod = beforeMethod.isAnnotationPresent(IgnoreMethod.class);\n            final boolean blockType = beforeMethod.getReturnType() == TraceBlock.class;\n            final Method afterMethod = searchMethod(targetInterceptorClazz, after, afterParamList);\n            if (afterMethod == null) {\n                throw new RuntimeException(after + \" method not found. \" + Arrays.toString(afterParamList));\n            }\n            final boolean afterIgnoreMethod = afterMethod.isAnnotationPresent(IgnoreMethod.class);\n\n            if (interceptorType == InterceptorType.RESULT_REPLACE && afterMethod.getReturnType() != Object.class) {\n                // a covariant override would change the weaved call descriptor and break the INVOKEINTERFACE site.\n                throw new RuntimeException(after + \" must return java.lang.Object. \" + targetInterceptorClazz.getName());\n            }\n\n            if (beforeIgnoreMethod && afterIgnoreMethod) {\n                return new DefaultInterceptorDefinition(interceptorClazz, targetInterceptorClazz, interceptorType, CaptureType.NON, null, null);\n            }\n            if (beforeIgnoreMethod) {\n                if (blockType) {\n                    throw new RuntimeException(before + \" not allowed return. \" + Arrays.toString(beforeParamList));\n                }\n                return new DefaultInterceptorDefinition(interceptorClazz, targetInterceptorClazz, interceptorType, CaptureType.AFTER, null, afterMethod);\n            }\n            if (afterIgnoreMethod) {\n                if (blockType) {\n                    throw new RuntimeException(after + \" not allowed return. \" + Arrays.toString(afterParamList));\n                }\n                return new DefaultInterceptorDefinition(interceptorClazz, targetInterceptorClazz, interceptorType, CaptureType.BEFORE, beforeMethod, null);\n            }\n            if (blockType) {","sourceCodeStart":176,"sourceCodeEnd":212,"githubUrl":"https://github.com/pinpoint-apm/pinpoint/blob/744c3d3075e595656abb1ae331ad2c0e4c9eb996/agent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/instrument/interceptor/InterceptorDefinitionFactory.java#L176-L212","documentation":"For InterceptorType.RESULT_REPLACE interceptors, the factory requires the after method to return java.lang.Object. A covariant return type (e.g. String) would change the weaved call descriptor and break the generated INVOKEINTERFACE call site, so the factory throws eagerly at definition time.","triggerScenarios":"Registering a RESULT_REPLACE interceptor whose after method returns a narrowed type (covariant override) instead of Object.","commonSituations":"Writing a result-replacement interceptor that returns the concrete business type for convenience; refactoring an after method from Object to a specific type; Java covariant override semantics silently narrowing the declared return type in a subclass.","solutions":["Change the after method return type to java.lang.Object","Cast the Object result at the call site where the replaced value is consumed","If a concrete return type is truly required, switch the interceptor to a non-RESULT_REPLACE InterceptorType"],"exampleFix":"// before\npublic String after(String target, String result, Object[] args) { ... }\n// after\npublic Object after(String target, Object result, Object[] args) { return result; }","handlingStrategy":"validation","validationCode":"if (type == InterceptorType.RESULT_REPLACE && !after.getReturnType().equals(Object.class)) throw new IllegalStateException(\"after must return Object\");","typeGuard":null,"tryCatchPattern":"try { def = factory.buildInterceptorDefinition(...); } catch (RuntimeException e) { log.error(\"RESULT_REPLACE after must return Object: {}\", e.getMessage()); throw e; }","preventionTips":["Always declare RESULT_REPLACE after methods as returning java.lang.Object","Avoid covariant return narrowing in interceptor subclasses","Add a unit test asserting after.getReturnType() == Object.class for result-replace interceptors"],"tags":["java","instrumentation","interceptor","return-type","result-replace"],"backgroundTag":"type-mismatch","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"}