Why Spring Transactions Fail: 7 Common Pitfalls and How to Fix Them

This article explores why Spring transactions may fail, covering seven common pitfalls such as incorrect method visibility, final methods, internal calls, missing Spring management, multithreading, unsupported table engines, and misconfigured propagation, and provides practical solutions to ensure reliable transaction handling.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Why Spring Transactions Fail: 7 Common Pitfalls and How to Fix Them

Preface

For Java developers, Spring transactions are familiar. In scenarios where a request needs to write to multiple tables, Spring transactions ensure atomicity. While @Transactional makes it easy, misuse can cause subtle problems.

1. Transaction Not Effective

1.1 Access Modifier Issue

Spring requires the proxied method to be public. If a @Transactional method is declared private, default, or protected, the transaction will be ignored because AbstractFallbackTransactionAttributeSource returns null for non‑public methods.

@Service
public class UserService {
    @Transactional
    private void add(UserModel userModel) {
        saveData(userModel);
        updateData(userModel);
    }
}

Therefore, the method must be public.

1.2 Final Method

Marking a transactional method as final prevents Spring AOP from creating a proxy that can override the method, so the transaction is not applied.

@Service
public class UserService {
    @Transactional
    public final void add(UserModel userModel) {
        saveData(userModel);
        updateData(userModel);
    }
}
Note: static methods also cannot be proxied for transactions.

1.3 Internal Method Call

Calling another @Transactional method from within the same class uses the current (this) instance, bypassing the proxy, so the second method runs without a transaction.

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public void add(UserModel userModel) {
        userMapper.insertUser(userModel);
        updateStatus(userModel);
    }

    @Transactional
    public void updateStatus(UserModel userModel) {
        doSameThing();
    }
}

Solutions include extracting the called method to another @Service, injecting the current bean, or using AopContext.currentProxy().

1.3.1 Extract to Another Service

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;

    public void save(User user) {
        queryData1();
        queryData2();
        serviceB.doSave(user);
    }
}

@Service
public class ServiceB {
    @Transactional(rollbackFor = Exception.class)
    public void doSave(User user) {
        addData1();
        updateData2();
    }
}

1.3.2 Inject Self

@Service
public class ServiceA {
    @Autowired
    private ServiceA serviceA;

    public void save(User user) {
        queryData1();
        queryData2();
        serviceA.doSave(user);
    }

    @Transactional(rollbackFor = Exception.class)
    public void doSave(User user) {
        addData1();
        updateData2();
    }
}

1.3.3 Use AopContext

@Service
public class ServiceA {
    public void save(User user) {
        queryData1();
        queryData2();
        ((ServiceA) AopContext.currentProxy()).doSave(user);
    }

    @Transactional(rollbackFor = Exception.class)
    public void doSave(User user) {
        addData1();
        updateData2();
    }
}

1.4 Not Managed by Spring

If a class is not annotated with @Service, @Component, etc., Spring will not create a bean, and @Transactional will have no effect.

// @Service
public class UserService {
    @Transactional
    public void add(UserModel userModel) {
        saveData(userModel);
        updateData(userModel);
    }
}

1.5 Multithreaded Calls

Transactional methods executed in a new thread do not share the same database connection, so they run in separate transactions and cannot roll back together.

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(UserModel userModel) throws Exception {
        userMapper.insertUser(userModel);
        new Thread(() -> {
            roleService.doOtherThing();
        }).start();
    }
}

@Service
public class RoleService {
    @Transactional
    public void doOtherThing() {
        System.out.println("Save role data");
    }
}

2. Transaction Not Rolling Back

2.1 Wrong Propagation Setting

Using Propagation.NEVER disables transaction creation, causing the method to run without a transaction.

@Service
public class UserService {
    @Transactional(propagation = Propagation.NEVER)
    public void add(UserModel userModel) {
        saveData(userModel);
        updateData(userModel);
    }
}

2.2 Swallowing Exceptions

Catching exceptions without re‑throwing prevents Spring from marking the transaction for rollback.

@Service
public class UserService {
    @Transactional
    public void add(UserModel userModel) {
        try {
            saveData(userModel);
            updateData(userModel);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }
}

2.3 Throwing Non‑Runtime Exceptions

By default Spring rolls back only on RuntimeException and Error. Throwing a checked Exception does not trigger rollback.

@Service
public class UserService {
    @Transactional
    public void add(UserModel userModel) throws Exception {
        try {
            saveData(userModel);
            updateData(userModel);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new Exception(e);
        }
    }
}

2.4 Misconfigured rollbackFor

If rollbackFor is set to a custom exception that never occurs, the transaction will not roll back for database exceptions.

@Service
public class UserService {
    @Transactional(rollbackFor = BusinessException.class)
    public void add(UserModel userModel) throws Exception {
        saveData(userModel);
        updateData(userModel);
    }
}

2.5 Nested Transaction Rollback Scope

When a nested transaction throws an exception that propagates outward, the outer transaction also rolls back. To isolate the rollback, catch the exception inside the outer method.

@Service
public class UserService {
    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(UserModel userModel) throws Exception {
        userMapper.insertUser(userModel);
        try {
            roleService.doOtherThing();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }
}

3. Other Topics

3.1 Large Transaction Problems

Annotating whole service methods with @Transactional can unintentionally include many read‑only queries, leading to long‑running transactions and performance issues. It is better to limit the transactional scope to the exact statements that modify data.

3.2 Programmatic Transactions

Spring also supports programmatic transaction management via TransactionTemplate, which offers finer control and avoids AOP proxy pitfalls.

@Autowired
private TransactionTemplate transactionTemplate;

public void save(final User user) {
    queryData1();
    queryData2();
    transactionTemplate.execute(status -> {
        addData1();
        updateData2();
        return Boolean.TRUE;
    });
}
Prefer TransactionTemplate for complex scenarios, but @Transactional remains convenient for simple, stable business logic.
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.

Backendtransactionaopspringtransactional
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

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.