pinpoint-apm/pinpoint · error · RuntimeException

${after} must return java.lang.Object. ${targetInterceptorCl

Error message

${after} must return java.lang.Object. ${targetInterceptorClazz.getName()}

What it means

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.

Source

Thrown at agent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/instrument/interceptor/InterceptorDefinitionFactory.java:194

        }

        private InterceptorDefinition createInterceptorDefinition(Class<? extends Interceptor> targetInterceptorClazz) {

            final Method beforeMethod = searchMethod(targetInterceptorClazz, before, beforeParamList);
            if (beforeMethod == null) {
                throw new RuntimeException(before + " method not found. " + Arrays.toString(beforeParamList));
            }
            final boolean beforeIgnoreMethod = beforeMethod.isAnnotationPresent(IgnoreMethod.class);
            final boolean blockType = beforeMethod.getReturnType() == TraceBlock.class;
            final Method afterMethod = searchMethod(targetInterceptorClazz, after, afterParamList);
            if (afterMethod == null) {
                throw new RuntimeException(after + " method not found. " + Arrays.toString(afterParamList));
            }
            final boolean afterIgnoreMethod = afterMethod.isAnnotationPresent(IgnoreMethod.class);

            if (interceptorType == InterceptorType.RESULT_REPLACE && afterMethod.getReturnType() != Object.class) {
                // a covariant override would change the weaved call descriptor and break the INVOKEINTERFACE site.
                throw new RuntimeException(after + " must return java.lang.Object. " + targetInterceptorClazz.getName());
            }

            if (beforeIgnoreMethod && afterIgnoreMethod) {
                return new DefaultInterceptorDefinition(interceptorClazz, targetInterceptorClazz, interceptorType, CaptureType.NON, null, null);
            }
            if (beforeIgnoreMethod) {
                if (blockType) {
                    throw new RuntimeException(before + " not allowed return. " + Arrays.toString(beforeParamList));
                }
                return new DefaultInterceptorDefinition(interceptorClazz, targetInterceptorClazz, interceptorType, CaptureType.AFTER, null, afterMethod);
            }
            if (afterIgnoreMethod) {
                if (blockType) {
                    throw new RuntimeException(after + " not allowed return. " + Arrays.toString(afterParamList));
                }
                return new DefaultInterceptorDefinition(interceptorClazz, targetInterceptorClazz, interceptorType, CaptureType.BEFORE, beforeMethod, null);
            }
            if (blockType) {

View on GitHub (pinned to 744c3d3075)

Solutions

  1. Change the after method return type to java.lang.Object
  2. Cast the Object result at the call site where the replaced value is consumed
  3. If a concrete return type is truly required, switch the interceptor to a non-RESULT_REPLACE InterceptorType

Example fix

// before
public String after(String target, String result, Object[] args) { ... }
// after
public Object after(String target, Object result, Object[] args) { return result; }
Defensive patterns

Strategy: validation

Validate before calling

if (type == InterceptorType.RESULT_REPLACE && !after.getReturnType().equals(Object.class)) throw new IllegalStateException("after must return Object");

Try / catch

try { def = factory.buildInterceptorDefinition(...); } catch (RuntimeException e) { log.error("RESULT_REPLACE after must return Object: {}", e.getMessage()); throw e; }

Prevention

When it happens

Trigger: Registering a RESULT_REPLACE interceptor whose after method returns a narrowed type (covariant override) instead of Object.

Common situations: 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.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of pinpoint-apm/pinpoint@744c3d3075 (2026-09-07). Data as JSON: /api/errors/b55bc00dd8f6a668. Report an issue: GitHub.