Analysis and Solutions for 'Transaction marked as rollbackOnly' Exception in Spring Transaction Management

Nov 28, 2025 · Programming · 11 views · 7.8

Keywords: Spring Transaction Management | Transaction Rollback | @Transactional Annotation

Abstract: This article provides an in-depth analysis of the common 'Transaction marked as rollbackOnly' exception in Spring framework. Through detailed code examples and transaction propagation mechanism analysis, it explains transaction handling issues in nested transaction scenarios. Starting from practical cases, the article elucidates the workflow of Spring transaction interceptors when transactional methods call other transactional methods and throw exceptions, offering multiple solutions and best practice recommendations to help developers better understand and handle complex scenarios in Spring transaction management.

Problem Background and Phenomenon Description

In application development based on Spring and Hibernate, transaction management is a core and complex topic. Many developers encounter a typical exception when handling nested transactions: Could not commit JPA transaction: Transaction marked as rollbackOnly. This exception typically occurs when a transactional method internally calls another transactional method and the inner method throws an exception.

Consider the following typical usage scenario: a service class is responsible for loading entities from the database, modifying their attribute values, and then committing changes after validation passes. If validation fails, developers hope to prevent data persistence by throwing an exception. However, this seemingly reasonable approach leads to exceptions during transaction commit.

In-depth Analysis of Exception Generation Mechanism

To understand the cause of this exception, we need to deeply analyze the working mechanism of Spring transaction interceptors. When a transactional method is called, Spring adds transaction management logic before and after method execution through AOP proxies.

Let's illustrate the complete process of problem generation through a specific code example:

@Service
class MyService {
    @Transactional(rollbackFor = MyCustomException.class)
    public void doSth() throws MyCustomException {
        // Load entities from database
        List<Entity> entities = entityRepository.findAll();
        
        // Modify entity attributes
        for (Entity entity : entities) {
            entity.setValue(newValue);
        }
        
        // Validate modified data
        if (!isValid(entities)) {
            throw new MyCustomException("Data validation failed");
        }
    }
}

class ServiceUser {
    @Autowired
    private MyService myService;
    
    @Transactional
    public void method() {
        try {
            myService.doSth();
        } catch (MyCustomException e) {
            // Exception handling logic
            logger.error("Business operation failed", e);
        }
    }
}

In this scenario, when the transaction execution process unfolds, the following key steps occur:

  1. Spring transaction interceptor intercepts the ServiceUser.method() method call, and since no active transaction exists, the interceptor starts a new transaction
  2. The method() method begins execution and calls myService.doSth()
  3. The transaction interceptor intercepts doSth() again, detects an existing active transaction, and thus joins the current transaction instead of creating a new one
  4. During execution of doSth(), MyCustomException is thrown
  5. The transaction interceptor catches this exception and, based on the @Transactional(rollbackFor = MyCustomException.class) configuration, marks the current transaction as rollbackOnly
  6. The exception is propagated to the method() method and caught and handled in the catch block
  7. When the method() method completes, the interceptor that initially started the transaction attempts to commit it, but Hibernate detects that the transaction has been marked as rollbackOnly, refuses to commit, and throws an exception

Root Cause and Solutions

The fundamental cause of the problem lies in incorrect division of transaction boundaries. When the outer method ServiceUser.method() is marked with @Transactional, it assumes responsibility for managing the entire transaction lifecycle. However, when a method within the transaction throws an exception and is marked for rollback, the outer method still attempts to commit this already "contaminated" transaction.

The most direct solution is to redesign the transaction boundaries. If ServiceUser.method() does not require transactionality, its @Transactional annotation should be removed:

class ServiceUser {
    @Autowired
    private MyService myService;
    
    public void method() {
        try {
            myService.doSth();
        } catch (MyCustomException e) {
            logger.error("Business operation failed", e);
        }
    }
}

After this adjustment, the transaction execution process becomes:

  1. The ServiceUser.method() method executes directly without transaction management involvement
  2. When calling myService.doSth(), the transaction interceptor detects no active transaction and starts a new transaction
  3. When doSth() throws an exception, the transaction interceptor directly rolls back the transaction and propagates the exception
  4. The outer method catches the exception and handles it, with no transaction commit conflicts occurring throughout the process

Advanced Transaction Management Strategies

Beyond the basic solution, Spring provides more granular transaction control options suitable for different business scenarios.

Transaction Propagation Behavior Control

By configuring different transaction propagation behaviors, transaction boundaries can be controlled more precisely:

@Service
class MyService {
    @Transactional(propagation = Propagation.REQUIRES_NEW, 
                   rollbackFor = MyCustomException.class)
    public void doSth() throws MyCustomException {
        // Business logic
    }
}

Using Propagation.REQUIRES_NEW ensures that the doSth() method always executes in a new transaction, completely isolated from the external transaction. This way, even if the inner transaction rolls back, it won't affect the commit of the outer transaction.

Exception Rollback Rule Customization

Spring allows developers to precisely control which exceptions should trigger transaction rollback:

@Transactional(noRollbackFor = BusinessException.class)
public void businessOperation() {
    // Some business exceptions should not cause transaction rollback
    if (someCondition) {
        throw new BusinessException("Business exception, but do not rollback transaction");
    }
}

Best Practice Recommendations

Based on deep understanding of Spring transaction mechanisms, we propose the following best practices:

  1. Clear Transaction Boundaries: Reasonably divide transaction boundaries at the service layer, avoiding unnecessary transaction nesting
  2. Unified Exception Handling Strategy: Standardize transactional exception handling approaches across the project
  3. Use Declarative Transactions: Prefer using @Transactional annotations for declarative transaction management
  4. Test Transaction Behavior: Write unit tests to verify correctness in complex transaction scenarios
  5. Monitor Transaction Performance: Pay attention to performance-related issues like transaction timeouts and deadlocks

Conclusion

The Transaction marked as rollbackOnly exception is a common but often misunderstood issue in Spring transaction management. By deeply understanding the working mechanism of Spring transaction interceptors and transaction propagation behaviors, developers can better design and implement robust transaction handling logic. The key lies in properly dividing transaction boundaries, ensuring that transaction startup and commit/rollback occur at the correct levels, and avoiding runtime exceptions caused by inconsistent transaction states.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.