How to intercept all method calls to a given object in ByteBuddy?

I have a Java class Fns with some public instance methods annotated with @Watch:

class Fns {
    @Watch
    int add(int x, int y) {
    }
}

Every time an instance method is called on an instance of Fns, e.g. fns.add(1, 2), I want to intercept that call with a method public static Object intercept(Object receiver, Method receiverMethod, Object[] args).

I have tried doing this in ByteBuddy, but most of the methods I tried rely on subclassing Fns, which then requires the subclass to call the superclass constructor (and the constructor and fields of Fns could take any form).

I just need a proxy class with the same methods – it doesn’t have to have the same constructor or fields. The intercept method needs to be able to call the original method (e.g. fns.add(1, 2) once it has done some work to log the method call.

How do I do this in ByteBuddy?

Here’s the straightforward answer:

Problem

You’re trying to intercept annotated instance method calls (@Watch) on a class Fns using ByteBuddy, without needing to subclass Fns or deal with its constructor or internal fields. You want to:

  • Intercept methods annotated with @Watch
  • Log or handle them via a static intercept(...) method
  • Still call the original method afterward
  • Avoid the complexities of subclassing Fns

Solution

Use ByteBuddy’s redefinition/decoration capabilities with method delegation, combined with an @RuntimeType interceptor.

You don’t need to subclass – you can redefine Fns at runtime and instrument its methods directly.

Step-by-step

  1. Define the annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Watch {}
  1. Create the interceptor class:
public class WatchInterceptor {

    @RuntimeType
    public static Object intercept(
        @This Object receiver,
        @Origin Method method,
        @AllArguments Object[] args,
        @SuperCall Callable<Object> originalMethod
    ) throws Exception {
        // Log or inspect here
        System.out.println("Intercepted: " + method.getName());

        // Optionally log args, receiver, etc.

        // Call the original method
        return originalMethod.call();
    }
}
  1. Redefine the Fns class using ByteBuddy:
new ByteBuddy()
    .redefine(Fns.class) // <-- Redefines the actual class, not a subclass
    .method(isAnnotatedWith(Watch.class)) // Target only methods with @Watch
    .intercept(MethodDelegation.to(WatchInterceptor.class))
    .make()
    .load(Fns.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());

You must have a Java agent installed for this to work with class redefinition. If you don’t, install the ByteBuddy agent:

ByteBuddyAgent.install();
  1. Usage:
Fns fns = new Fns();
fns.add(1, 2); // This will now be intercepted

Notes

  • This approach modifies the original Fns class at runtime, so you don’t need to create a proxy or deal with constructors.
  • You’re delegating only the @Watch-annotated methods to your interceptor.
  • The @SuperCall Callable<Object> originalMethod gives you a handle to call the actual original method after interception.

Summary

  • Use redefine() not subclass()
  • Target methods with @Watch using isAnnotatedWith(...)
  • Delegate to a static interceptor with MethodDelegation.to(...)
  • Use @SuperCall to invoke the original method
  • Install the ByteBuddy agent if you haven’t

Let me know if you need help wiring this up in a Spring or JUnit context.