There is one of the most common (and subtle) pitfalls of Spring’s @Transactional
: self-invocation, which can silently break transactional (and other proxy-based) behavior.
🔎 What is self-invocation?
It’s when a method in a Spring bean calls another method on the same bean instance, like:
@Service
public class MyService {
@Transactional
public void outer() {
inner(); // self-invocation
}
@Transactional
public void inner() {
...
}
}
Here, calling inner()
from outer()
happens directly on this
, bypassing the Spring proxy → annotations like @Transactional
on inner()
are ignored.
✅ How to work around self-invocation?
✅ 1) Extract to another Spring bean
Move the inner()
method to a separate @Service
or @Component
:
@Service
public class InnerService {
@Transactional
public void inner() {
...
}
}
@Service
@RequiredArgsConstructor
public class OuterService {
private final InnerService innerService;
@Transactional
public void outer() {
innerService.inner(); // goes through Spring proxy → transactional works
}
}
✔ Recommended for clean separation of concerns.
✅ 2) Inject self-proxy
Inject the current bean as a proxy into itself and call methods through it:
@Service
public class MyService {
@Autowired
private MyService selfProxy;
@Transactional
public void outer() {
selfProxy.inner(); // calls through proxy → transactional applies
}
@Transactional
public void inner() {
...
}
}
✔ Works, but can be confusing and couples the class to itself.
✅ 3) Use AopContext
(advanced)
Enable exposeProxy=true
on your @EnableTransactionManagement
configuration, then get the proxy dynamically:
@EnableTransactionManagement(exposeProxy = true)
@Configuration
public class AppConfig {}
@Service
public class MyService {
@Transactional
public void outer() {
((MyService) AopContext.currentProxy()).inner(); // calls through proxy
}
@Transactional
public void inner() {
...
}
}
✔ Handy but a bit hacky; avoid unless you really need it.
🔥 Bottom line:
The best practice is usually extracting methods to another Spring bean and calling through it → clear, maintainable, and doesn’t rely on Spring-specific tricks.