Why Does @Transactional(REQUIRES_NEW) Still Roll Back Your Audit Log?

When a Spring @Transactional method with REQUIRES_NEW fails to persist audit logs after a rollback, the issue often stems from misconfigured rollback rules, caught exceptions, self‑invocation, non‑public or final methods, or incorrect propagation settings, all of which prevent the new transaction from committing.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Why Does @Transactional(REQUIRES_NEW) Still Roll Back Your Audit Log?

Developers often annotate a user‑registration method with @Transactional and expect an audit log written in a method annotated with @Transactional(propagation = Propagation.REQUIRES_NEW) to survive a rollback of the outer transaction. In practice the log may disappear, leading to confusion.

Ideal Flow (Normal Situation)

@Transactional
public void registerUser(User user) {
    saveUser(user); // main transaction work
    logService.log("User registration attempt"); // calls external bean, triggers REQUIRES_NEW
}

@Service
public class LogService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log(String message) {
        logMapper.insert(new Log(message));
    }
}

In this scenario, even if registerUser throws an exception and its transaction rolls back, the log() method runs in an independent transaction that has already been committed, so the audit record remains.

Why the Log May Still Disappear

1. rollbackFor Not Set Correctly

Spring rolls back only for RuntimeException and Error by default. If a checked exception such as IOException is thrown and rollbackFor is not specified, the transaction will not roll back.

@Service
public class LogService {
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void log(String message) {
        try {
            logMapper.insert(new Log(message));
        } catch (Exception e) {
            throw new AuditLogException("Audit log failed", e);
        }
    }
}

2. Exception Caught but Not Rethrown

If an exception is caught inside the REQUIRES_NEW method and not rethrown, Spring assumes the method completed successfully and will not trigger a rollback.

@Service
public class LogService {
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void log(String message) {
        try {
            logMapper.insert(new Log(message));
        } catch (Exception e) {
            log.error("Error occurred: ", e); // caught but not rethrown → no rollback
        }
    }
}

3. Self‑Invocation Within the Same Class

Spring creates a proxy for transactional beans. When a method in the same class calls another @Transactional method (self‑invocation), the call bypasses the proxy, so the inner transaction never starts.

@Transactional
public void registerUser(User user) {
    saveUser(user);
    log("User registration attempt"); // self‑call → REQUIRES_NEW ignored
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String message) {
    logMapper.insert(new Log(message));
}

4. @Transactional on Final or Non‑Public Methods

JDK dynamic proxies can only intercept public interface methods; CGLIB can proxy protected or package‑private methods but not final methods. Declaring a transactional method as final or non‑public prevents the proxy from applying the transaction.

@Service
public class LogService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    private void log(String message) { // private → proxy cannot intercept
        logMapper.insert(new Log(message));
    }
    // or
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public final void log(String message) { // final → CGLIB cannot proxy
        logMapper.insert(new Log(message));
    }
}

5. Wrong Propagation Configuration

If the propagation attribute is mistakenly set to Propagation.REQUIRES instead of REQUIRES_NEW, the method will join the existing transaction rather than start a new one, causing the log to roll back together with the outer transaction.

@Service
public class LogService {
    @Transactional(propagation = Propagation.REQUIRES) // should be REQUIRES_NEW
    public void log(String message) {
        logMapper.insert(new Log(message));
    }
}

Solution

To ensure the audit log persists, apply one or more of the following:

Place the REQUIRES_NEW method in a separate bean so the proxy can intercept the call.

Configure rollbackFor to include the exceptions you expect.

Rethrow caught exceptions or wrap them in a runtime exception.

Avoid self‑invocation; use ApplicationContext.getBean(...) to obtain the proxy.

Do not declare transactional methods as final or non‑public.

Verify that the propagation attribute is correctly set to REQUIRES_NEW.

By addressing these common pitfalls, the independent transaction will commit as intended, and audit logs will remain even when the outer transaction rolls back.

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.

JavaProxyspringTransactionalrollbackexceptionhandlingREQUIRES_NEW
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

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.