Backend Development 20 min read

Spring Transaction Pitfalls: Common Scenarios Where Transactions Fail and How to Fix Them

This article explains the most common reasons why Spring @Transactional annotations may become ineffective or fail to roll back, covering access‑modifier issues, final methods, internal calls, self‑injection, multithreading, unsupported table engines, mis‑configured propagation, exception handling, nested transactions, large‑transaction problems, and the advantages of programmatic transaction management.

IT Services Circle
IT Services Circle
IT Services Circle
Spring Transaction Pitfalls: Common Scenarios Where Transactions Fail and How to Fix Them

For Java developers, Spring transaction management is a familiar tool, but it can silently fail if used incorrectly. This article lists the typical situations where @Transactional does not work as expected and provides practical solutions.

1. Transaction Not Effective

1.1 Access‑modifier problem

Spring only creates proxies for public methods. If a transactional method is declared private , protected or package‑private, Spring returns null for the transaction attribute, so no transaction is applied.

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

1.2 Method declared final

When a method is final , the generated proxy cannot override it, therefore the transactional logic is never inserted.

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

1.3 Internal method call

Calling a transactional method from another method of the same class uses the this reference, bypassing the proxy. Consequently the inner method runs without a transaction.

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

    public void add(UserModel userModel) {
        userMapper.insertUser(userModel);
        updateStatus(userModel); // no transaction here
    }

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

1.4 Adding a separate service

Move the transactional logic to another @Service bean and inject it, so the call goes through the proxy.

@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.5 Self‑injection

Inject the bean into itself (Spring’s three‑level cache prevents circular‑dependency problems) and call the transactional method via the injected proxy.

@Service
public class ServiceA {
    @Autowired
    private ServiceA self;
    public void save(User user) {
        queryData1();
        queryData2();
        self.doSave(user);
    }
    @Transactional(rollbackFor = Exception.class)
    public void doSave(User user) {
        addData1();
        updateData2();
    }
}

1.6 Using AopContext.currentProxy()

Obtain the current proxy programmatically and invoke the transactional method through it.

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

1.7 Bean not managed by Spring

If a class lacks @Component , @Service , @Repository or similar annotations, Spring will not create a proxy, and @Transactional has no effect.

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

1.8 Multithreading

Transactional context is bound to the current thread. When a new thread is started, it receives a different database connection, so the operations run in separate transactions and cannot roll back together.

@Service
public class UserService {
    @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");
    }
}

1.9 Table engine does not support transactions

MyISAM tables (MySQL 5 and earlier) cannot participate in transactions. Using such tables will make any @Transactional annotation ineffective.

CREATE TABLE `category` (
  `id` BIGINT NOT NULL AUTO_INCREMENT,
  `one_category` VARCHAR(20) DEFAULT NULL,
  ...
) ENGINE=MyISAM;

1.10 Transaction not enabled

In a plain Spring project you must configure a transaction manager and enable @Transactional via XML or Java config. Spring Boot does this automatically through DataSourceTransactionManagerAutoConfiguration .

2. Transaction Not Rolling Back

2.1 Wrong propagation attribute

Using Propagation.NEVER or other non‑creating propagation types prevents a transaction from being started, so rollback never occurs.

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

2.2 Swallowing exceptions

If the method catches the exception and does not re‑throw it, Spring assumes the operation succeeded and will not roll back.

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

2.3 Throwing a non‑runtime exception

By default Spring rolls back only on RuntimeException and Error . Throwing a checked Exception will not trigger a rollback unless rollbackFor is configured.

@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 Mis‑configured rollbackFor

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

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

2.5 Nested transaction roll‑back behavior

When an inner transaction (propagation = NESTED) throws an exception that propagates outward, the outer transaction also rolls back, unless the exception is caught and suppressed.

@Transactional
public void add(UserModel userModel) throws Exception {
    userMapper.insertUser(userModel);
    roleService.doOtherThing(); // NESTED transaction
}

@Service
public class RoleService {
    @Transactional(propagation = Propagation.NESTED)
    public void doOtherThing() {
        System.out.println("save role data");
    }
}

3. Other Important Topics

3.1 Large‑transaction problem

Annotating a whole service method with @Transactional may unintentionally include many read‑only queries, leading to long‑running transactions and performance issues. It is better to isolate the transactional part into a dedicated method or service.

3.2 Programmatic (declarative vs. programmatic) transactions

Spring also provides TransactionTemplate for programmatic transaction control, which avoids AOP proxy pitfalls and gives finer‑grained scope.

@Autowired
private TransactionTemplate transactionTemplate;

public void save(final User user) {
    queryData1();
    queryData2();
    transactionTemplate.execute(status -> {
        addData1();
        updateData2();
        return Boolean.TRUE;
    });
}

While @Transactional is convenient for simple cases, using TransactionTemplate is recommended for complex or performance‑critical scenarios.

JavaTransactionAOPdatabaseSpringTransactionalPropagationExceptionHandling
IT Services Circle
Written by

IT Services Circle

Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.

0 followers
Reader feedback

How this landed with the community

login 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.