Zum Hauptinhalt springen
AI & KI NachrichtenF.A.A. to Roll Out New A.I. Tool for Washington Airports(18.09.2026 um 22:20 Uhr)
AI & KI NachrichtenBritish data centre group Nscale files for $35bn US listing(18.09.2026 um 22:26 Uhr)
AI & KI NachrichtenAmazon SageMaker Inference: 2026 year-to-date launches in review(18.09.2026 um 22:52 Uhr)
YouTube Security VideosLinus Tech Tips: Building the Rarest PC(18.09.2026 um 21:52 Uhr)
Linux Tipps & HardeningWhy I prefer systemd Timers(18.09.2026 um 22:08 Uhr)
Sichere ProgrammierungSanity Studio + Next.js starter with a public production dataset(18.09.2026 um 22:09 Uhr)
AI & KI NachrichtenF.A.A. to Roll Out New A.I. Tool for Washington Airports(18.09.2026 um 22:20 Uhr)
AI & KI NachrichtenBritish data centre group Nscale files for $35bn US listing(18.09.2026 um 22:26 Uhr)
AI & KI NachrichtenAmazon SageMaker Inference: 2026 year-to-date launches in review(18.09.2026 um 22:52 Uhr)
YouTube Security VideosLinus Tech Tips: Building the Rarest PC(18.09.2026 um 21:52 Uhr)
Linux Tipps & HardeningWhy I prefer systemd Timers(18.09.2026 um 22:08 Uhr)
Sichere ProgrammierungSanity Studio + Next.js starter with a public production dataset(18.09.2026 um 22:09 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Why `@Transactional` “Doesn’t Work” in Spring: 7 Proxy Gotchas (and Fixes)

You add @Transactional, expect a rollback… and the data still commits.

Most of the time, Spring transactions are working exactly as designed — it’s just that your code never actually enters a transactional boundary. The reason is simple:

Spring usually applies transactions via AOP proxies.

If your call bypasses the proxy, the transaction advice never runs.

Here are 7 common gotchas I see in real projects, plus practical fixes.

0) Quick mental model: where transactions actually start

With proxy-based AOP, a transaction starts when:

  • A Spring-managed bean method is called
  • and that call goes through the proxy
  • and the method has transactional metadata (@Transactional)

So the key question when debugging is always:

“Did this method call go through the Spring proxy?”

1) Self-invocation: calling your own method bypasses the proxy

This is the #1 reason people say “transaction didn’t work”.

@Service
public class OrderService {

  public void placeOrder() {
    saveOrder(); // self-invocation -> no proxy -> @Transactional not applied
  }

  @Transactional
  public void saveOrder() {
    // write to DB
    throw new RuntimeException("boom"); // you expect rollback
  }
}

What happens?

placeOrder() calls saveOrder() directly on this. No proxy involved. No transaction.

✅ Fix options (pick one):

Fix A (recommended): move transactional method to another bean

@Service
public class OrderWriter {
  @Transactional
  public void saveOrder() { ... }
}

@Service
public class OrderService {
  private final OrderWriter writer;
  public OrderService(OrderWriter writer) { this.writer = writer; }

  public void placeOrder() {
    writer.saveOrder(); // goes through proxy
  }
}

Fix B: use TransactionTemplate for programmatic transactions

@Service
public class OrderService {
  private final TransactionTemplate tx;

  public OrderService(PlatformTransactionManager tm) {
    this.tx = new TransactionTemplate(tm);
  }

  public void placeOrder() {
    tx.executeWithoutResult(status -> {
      // write to DB
      if (somethingBad()) throw new RuntimeException("boom");
    });
  }
}

2) Method visibility: non-public methods often won’t be transactional

By default (proxy-based AOP), Spring typically only applies @Transactional to public methods.

@Service
public class BillingService {

  @Transactional
  void charge() { // package-private: often ignored
    ...
  }
}

✅ Fix: make the transactional boundary public, or switch to AspectJ weaving (advanced).

@Transactional
public void charge() { ... }

3) final classes/methods: proxies can’t override them

If Spring uses CGLIB class-based proxies, final prevents overriding.

@Service
public final class PaymentService {

  @Transactional
  public void pay() { ... } // cannot be proxied if class/method is final
}

✅ Fix:

  • remove final, or
  • use interface-based proxies (JDK proxy) and call through the interface, but final methods still can’t be advised.

In practice: avoid final on Spring service classes/methods that need AOP.

4) Checked exceptions don’t rollback by default

By default, Spring rolls back on:

  • RuntimeException
  • Error

…but not on checked exceptions.

@Transactional
public void ship() throws Exception {
  // write DB
  throw new Exception("checked"); // by default: transaction may COMMIT
}

✅ Fix: configure rollback rules explicitly.

@Transactional(rollbackFor = Exception.class)
public void ship() throws Exception {
  ...
}

Or throw a runtime exception intentionally (be consistent in your codebase).

5) Catching exceptions (and swallowing them) commits the transaction

This one is sneaky.

@Transactional
public void updateProfile() {
  try {
    // write to DB
    riskyOperation();
  } catch (Exception e) {
    // log and swallow
  }
  // method ends normally -> transaction commits
}

✅ Fix options:

Fix A: rethrow

catch (Exception e) {
  throw e;
}

Fix B: mark rollback-only

catch (Exception e) {
  TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}

Use rollback-only sparingly — rethrowing is often clearer.

6) @PostConstruct / constructor calls: proxy isn’t ready yet

If you call transactional methods during bean initialization, you can bypass the proxy lifecycle.

@Service
public class WarmUpService {

  @PostConstruct
  public void init() {
    warmUp(); // often not transactional the way you expect
  }

  @Transactional
  public void warmUp() { ... }
}

✅ Fix: run warmup after context is fully ready:

  • ApplicationReadyEvent
  • CommandLineRunner
  • scheduling after startup
@Component
public class WarmUpRunner implements ApplicationRunner {
  private final WarmUpService svc;
  public WarmUpRunner(WarmUpService svc) { this.svc = svc; }

  @Override
  public void run(ApplicationArguments args) {
    svc.warmUp(); // goes through proxy
  }
}

7) Async / new threads: transactions are thread-bound

A transaction is bound to the current thread. If you start a new thread, it won’t share the same transaction context.

@Transactional
public void process() {
  // write in tx
  CompletableFuture.runAsync(() -> {
    // NOT in the same tx
    // writes here are separate
  });
}

✅ Fix patterns:

  • Make the async method start its own transaction
  • Or use messaging / outbox pattern for consistency
  • Or redesign: keep DB transaction small, push slow work out of it

A quick debugging checklist

When you suspect “transaction didn’t work”, check these in order:

  1. Is the method public?
  2. Is it called from another bean (through proxy), not this.?
  3. Any final class/method involved?
  4. Did you throw a runtime exception? Or a checked exception?
  5. Did you catch and swallow exceptions?
  6. Was it invoked during initialization (@PostConstruct)?
  7. Any async/thread boundaries?

Also helpful: enable transaction logs (for Spring Boot):

logging.level.org.springframework.transaction=DEBUG
logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG

You should see logs like “Creating new transaction” / “Rolling back” / “Committing”.

Recommended rule of thumb

If you want fewer surprises:

  • Put transaction boundaries on public service methods
  • Keep them short
  • Avoid calling transactional methods inside the same class
  • Don’t mix DB transactions with slow network calls
  • Use TransactionTemplate if you need explicit control
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Why `@Transactional` “Doesn’t Work” in Spring: 7 Proxy Gotchas (and Fixes)

Thematisch verwandte Begriffe: Transactional, Doesnt, Work, Spring · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-61591 | djust provides Phoenix LiveView-style reactive server-side rendering for…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Rechts: Artikel Ziehen Links: RSS
Hoch: nächster Artikel Runter: zurück / schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Rechts: Original Links: RSS-Ansicht
↗ Original-Quelle
Social Reaktionen Stimme abgeben (+5 Karma)
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick