Why @Transactional Sometimes Fails in Spring and How to Fix It

This article explains three common situations where Spring's @Transactional annotation becomes ineffective—non‑public methods, internal self‑calls, and swallowed exceptions—detailing the underlying proxy mechanism, code examples, test results, and practical workarounds to ensure proper transaction management.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Why @Transactional Sometimes Fails in Spring and How to Fix It

Transactional Annotation Failure Scenarios

The @Transactional annotation can become ineffective in three typical cases: when the annotated method is not public , when a transactional method is invoked internally within the same class, and when the method catches exceptions without re‑throwing them.

1. Non‑public Method Modifier

Spring creates transaction proxies only for public methods. If a method is declared with default, protected, or private visibility, the proxy does not apply and the transaction never starts.

/**
 * @author zhoujy
 */
@Component
public class TestServiceImpl {
    @Resource
    TestMapper testMapper;

    @Transactional
    void insertTestWrongModifier() { // not public
        int re = testMapper.insert(new Test(10,20,30));
        if (re > 0) {
            throw new NeedToInterceptException("need intercept");
        }
        testMapper.insert(new Test(210,20,30));
    }
}

A test invoking this method shows that the testMapper.insert(new Test(10,20,30)) operation is not rolled back when an exception is thrown.

2. Internal Self‑Invocation

When a transactional method is called from another method of the same class, the call bypasses the proxy (it uses this), so the transaction logic is skipped.

public class TestServiceImpl implements TestService {
    @Transactional
    public void insertTestInnerInvoke() {
        // transactional logic
    }

    public void testInnerInvoke() {
        // internal call – no proxy
        insertTestInnerInvoke();
    }
}

The corresponding test demonstrates that the database insert is not rolled back. A common workaround is to inject the bean into itself and call the method through the injected proxy:

@Resource
private TestServiceImpl self;

public void testInnerInvoke() {
    self.insertTestInnerInvoke(); // goes through proxy
}

3. Swallowing Exceptions

If a transactional method catches an exception and does not re‑throw it, Spring never sees the exception, so the transaction is committed instead of rolled back.

@Transactional
public void insertTestCatchException() {
    try {
        int re = testMapper.insert(new Test(10,20,30));
        if (re > 0) {
            throw new NeedToInterceptException("need intercept");
        }
        testMapper.insert(new Test(210,20,30));
    } catch (Exception e) {
        System.out.println("i catch exception"); // exception swallowed
    }
}

The test shows that even though NeedToInterceptException is thrown, the subsequent insert is not rolled back because the exception is handled inside the method.

Underlying Proxy Mechanics

Spring scans beans for @Transactional annotations using BeanFactoryTransactionAttributeSourceAdvisor. The canApply method iterates over all declared methods; if a method is non‑public,

AbstractFallbackTransactionAttributeSource#computeTransactionAttribute

returns null, preventing proxy creation for that bean.

During method interception, CglibAopProxy.DynamicAdvisedInterceptor#intercept retrieves the interceptor chain via advised.getInterceptorsAndDynamicInterceptionAdvice. If the target method lacks a transactional attribute, the chain is empty and the original method executes without transaction management.

When a transaction is active, TransactionAspectSupport#invokeWithinTransaction opens the transaction, invokes the method, and rolls back in the catch block. Swallowed exceptions or bypassed proxies prevent this rollback logic from running.

Summary

In summary, @Transactional fails when the method is not public, when internal self‑calls bypass the proxy, or when exceptions are caught and not re‑thrown. Understanding Spring's AOP proxy creation and interception flow helps developers avoid these pitfalls and ensure reliable transaction management.

CGLIB proxy example
CGLIB proxy example
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.

JavaaopdatabaseException Handlingspringtransactional
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.