Why Spring Transactions Fail with JDK Dynamic Proxies and How to Fix Them

This article examines how JDK dynamic proxies cause Spring transaction methods to lose their transactional behavior, illustrates the issue with four concrete scenarios, and presents three practical solutions to ensure proper rollback and commit semantics.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Why Spring Transactions Fail with JDK Dynamic Proxies and How to Fix Them

1. Scenario Analysis

We set up two transactions, parent() and child(), and call them from a controller. Four scenarios (A‑D) demonstrate different outcomes when child() throws an exception.

Scenario A and B execute both methods successfully, inserting data in both tables.

In Scenario C the child() method is modified to throw an exception without being caught; both transactions roll back and no data is inserted.

Scenario D catches the exception in parent(). Both parent() and child() commit successfully, contrary to the expectation that only child() should roll back.

2. Root Cause

Spring transaction management relies on JDK dynamic proxies (or CGLIB). When parent() calls child() directly, the call bypasses the proxy, so the transactional annotation on child() is ignored. Consequently, in Scenario C the exception propagates to parent() causing a full rollback, and in Scenario D the caught exception prevents any rollback, so both methods commit.

Illustration of the proxy mechanism:

3. How the Proxy Works

Only the method invoked on the proxy object itself goes through the invoke() interceptor. Internal method calls (e.g., this.child()) are plain Java calls and do not trigger the proxy. proxy Therefore, transactional behavior is applied only when the call originates from the proxy.

4. Solutions

1. Refactor code to avoid internal transactional calls; place transactional boundaries at a higher service level.

2. Expose the proxy and obtain it via AopContext.currentProxy() (Spring Boot: @EnableAspectJAutoProxy(exposeProxy = true)). <aop:aspectj-autoproxy expose-proxy="true"/> 3. Retrieve the bean from ApplicationContext and invoke the method on that bean, ensuring the call goes through the proxy.

5. Summary

JDK dynamic proxies cause Spring transactions to be ineffective when a transactional method calls another transactional method within the same class. Understanding this proxy limitation and applying one of the three remedies prevents unexpected rollbacks and ensures correct transaction semantics.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javaaopDynamic Proxytransaction-managementJDK Proxy
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.