BackendBench
Coffee

Unravel › Java concepts › Spring and Boot

Spring

Spring proxies (how annotations work)

Annotations like @Transactional, @Async and @Cacheable work through a proxy: Spring wraps your bean in a stand-in object that adds behaviour before and after your method.

Learn these first

At a glance

Mental model

The proxy is a receptionist standing in front of the expert.

What happens, in order
  1. 1When Spring sees an annotation that needs extra behaviour, it creates a subclass or a JDK dynamic proxy of your bean and registers that insteadOther beans receive the proxy.
  2. 2A call from outside goes proxy, then your methodThe proxy starts a transaction, checks a cache, or hands the call to a thread pool, and then delegates to the real object.
  3. 3A call from inside the same class (this.other()) skips the proxy because this is the real objectThe annotation on other() silently does nothing. This is the famous self-invocation problem.
Remember
Watch out
Self-invocation of a @Transactional, @Async or @Cacheable methodAnnotating private or final methodsExpecting annotations to work on objects created with new
Example (highlighted lines explain it)
@Servicepublic class ReportService {    @Cacheable("reports")    public Report build(long customerId) {        return expensiveQuery(customerId);   // skipped when the cache already has the answer    }    public void refresh(long customerId) {        build(customerId);                   // same-class call: the cache is bypassed    }}
  1. line 3 uses Cacheable, the core of Spring proxies (how annotations work)
  2. line 5 skipped when the cache already has the answer
  3. line 9 same-class call: the cache is bypassed
Read the full explanation (about 2 min)

Annotations like @Transactional, @Async and @Cacheable work through a proxy: Spring wraps your bean in a stand-in object that adds behaviour before and after your method. Calls that bypass the proxy bypass the behaviour.

A mental model

The proxy is a receptionist standing in front of the expert. Every visitor goes through the receptionist, who opens a file, lets them in and closes the file afterwards. If the expert talks to themselves, no file is ever opened.

How it works

When Spring sees an annotation that needs extra behaviour, it creates a subclass or a JDK dynamic proxy of your bean and registers that instead. Other beans receive the proxy.

A call from outside goes proxy, then your method. The proxy starts a transaction, checks a cache, or hands the call to a thread pool, and then delegates to the real object.

A call from inside the same class (this.other()) skips the proxy because this is the real object. The annotation on other() silently does nothing. This is the famous self-invocation problem.

Going deeper

Practical rules: annotated methods must be public and not final or private (the proxy cannot override them), the bean must be created by Spring, and the call must come from another bean.

The fix for self-invocation is to move the method to another bean, or to restructure so the proxy is on the call path. Injecting the bean into itself is a workaround, not a design.

@Aspect classes let you add your own such behaviour (logging, timing, auditing) with pointcut expressions.

Under the hood

Classes without interfaces are proxied by CGLIB, which generates a subclass at runtime; classes with interfaces can use JDK dynamic proxies (java.lang.reflect.Proxy). Spring Boot defaults to CGLIB. The proxy holds a chain of interceptors; each annotation contributes one advisor to that chain.

Common mistakes

  • Self-invocation of a @Transactional, @Async or @Cacheable method.
  • Annotating private or final methods.
  • Expecting annotations to work on objects created with new.

How Unravel recognises it

When you paste code, Unravel looks for these signs of spring proxies (how annotations work). Spotting them yourself is a good habit when you read unfamiliar code:

Part of a learning path

Related concepts

Builds on this

Frequently asked questions

What is Spring proxies (how annotations work)?

Annotations like @Transactional, @Async and @Cacheable work through a proxy: Spring wraps your bean in a stand-in object that adds behaviour before and after your method. Calls that bypass the proxy bypass the behaviour.

What are the most common mistakes with Spring proxies (how annotations work)?

Self-invocation of a @Transactional, @Async or @Cacheable method. Annotating private or final methods. Expecting annotations to work on objects created with new.

How can I see Spring proxies (how annotations work) in real code?

Paste your own Java into Unravel and it will point out where spring proxies (how annotations work) appears, explain how it works at the detail level you choose, and draw the flow of the code around it.