Common Scenarios Where Spring Transactions Fail and How to Fix Them

This article explains why Spring @Transactional may become ineffective or fail to roll back in various situations—such as wrong method visibility, final modifiers, internal calls, missing Spring bean registration, multithreading, unsupported table engines, misconfigured propagation, swallowed exceptions, and improper rollback settings—while also offering practical solutions and best‑practice recommendations.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Common Scenarios Where Spring Transactions Fail and How to Fix Them

For Java developers, Spring transactions are a familiar tool, but they can silently fail in many scenarios, leading to data inconsistency.

1. Transaction Not Effective

1.1 Access modifier issues – Spring only proxies public methods; using private, protected or default visibility disables the transaction.

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

The

AbstractFallbackTransactionAttributeSource.computeTransactionAttribute

method returns null for non‑public methods, so no transaction is created.

1.2 Final methods – A final method cannot be overridden by the CGLIB/JDK proxy, thus the transaction logic is never applied.

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

1.3 Internal method calls – Calling another @Transactional method from the same class uses this, 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); // internal call – no transaction
    }

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

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

1.4 Not managed by Spring – Classes without @Component/@Service/@Controller annotations are not beans; their @Transactional methods are never proxied.

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

1.5 Multithreading – Transactions are bound to the current thread. Executing transactional logic in a new thread creates a separate connection, so rollback cannot propagate to the original thread.

@Slf4j
@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");
    }
}

1.6 Table engine does not support transactions – MyISAM tables cannot participate in transactions; using InnoDB (or another transactional engine) is required.

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

1.7 Transaction not enabled – In a plain Spring project you must declare a DataSourceTransactionManager and AOP advice; Spring Boot configures it automatically.

<!-- Configure transaction manager -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="advice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>
<aop:config>
    <aop:pointcut expression="execution(* com.susan.*.*(..))" id="pointcut"/>
    <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>

2. Transaction Does Not Roll Back

2.1 Wrong propagation – Using Propagation.NEVER or other non‑creating propagation prevents a transaction from being started.

@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 them makes Spring think the method succeeded, so no rollback occurs.

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

2.3 Throwing checked exceptions – By default Spring rolls back only RuntimeException and Error. Throwing a plain Exception will not trigger rollback unless rollbackFor is set.

@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); // still a checked exception → no rollback
    }
}

2.4 Mis‑configured rollbackFor – Specifying a custom exception that is never thrown (e.g., BusinessException) means real database errors will not cause a rollback.

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

It is safer to use rollbackFor = Exception.class or Throwable.class.

2.5 Nested transaction rollback confusion – When an inner @Transactional(propagation = Propagation.NESTED) method throws an exception, the outer transaction also rolls back unless the exception is caught inside the outer method.

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

3. Other Topics

3.1 Large‑transaction problem – Annotating an entire service method with @Transactional may include many read‑only queries, causing the transaction to stay open for a long time and hurting performance.

3.2 Programmatic transaction – Instead of relying on annotation‑driven AOP, you can use TransactionTemplate to define the exact scope of a transaction, which avoids many proxy‑related pitfalls.

@Autowired
private TransactionTemplate transactionTemplate;

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

In summary, understanding the limitations of Spring’s declarative transaction mechanism and applying the appropriate fixes—correct method visibility, avoiding final methods, using proxies for internal calls, ensuring beans are managed, handling multithreading carefully, choosing a transactional table engine, configuring propagation and rollback rules correctly, and considering programmatic transactions for fine‑grained control—will greatly reduce the risk of transaction failures in backend Java applications.

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.

BackendJavatransactionaopspringtransactional
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.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.