Learned about @Async in Spring Boot: proxy requirement for parallel execution

Today I learned something interesting about Spring Boot’s @Async annotation. I was debugging why an async method wasn’t actually running in parallel — turns out, it was because I was calling it from the same class, so Spring’s proxy never intercepted the method call. Lesson: @Async only works when the method is called through the Spring proxy, not directly inside the same bean. Moving the method into a separate service (or injecting self via proxy) fixed the issue instantly. It’s a small reminder that framework features are just smart proxies under the hood — and understanding how they work saves hours of debugging. Have you hit similar proxy or async issues in Spring Boot? #TodayILearned #SpringBoot #Java #Microservices #BackendDevelopment #LearningInPublic

This brings back memories from a project I worked on about four years ago. Our tech lead spent three intense days (and nights!) trying to figure out why a transactional method wasn’t working. The culprit? The method was being called from the same class, so the @Transactional proxy never kicked in. When he finally found out, he couldn’t believe how such a small detail caused so much pain. We’ve all been there!

Like
Reply

I’d add one more interesting scenario involving @Async and @Transactional that I ran into. If you have a circular dependency between two beans and you resolve it using @Autowired (for example, using field injection or @Lazy), you may notice an unexpected behavior: @Transactional will still work fine, because Spring can still create the proxy and wrap the method But @Async will fail at runtime with a circular dependency error, because the async proxy can't be created in this situation It was a subtle issue — everything compiled and looked correct — but at runtime the app would break only when the @Async method was involved.

Like
Reply

Another debugging headache: Async methods don’t always keep the parent request context when creating child requests. You can use an inheritable thread-local to share the request context, but it’s not reliable when there’s a lot of concurrent activity. It’s better to pass the parent request context directly as a parameter to the async method and set it again inside the child, so it stays available for the whole process.

See more comments

To view or add a comment, sign in

Explore content categories