# @Transactional: propagation, isolation, and the six silent failures Companion project for [**@Transactional in Spring**](https://ankurm.com/) on ankurm.com. Every row of the propagation matrix was produced by calling the method and asking the transaction manager what it did — not by reading the enum. ## Versions | | | |---|---| | Spring Boot | 4.1.1 | | Spring Framework | 7.0.9 | | JDK | Eclipse Temurin 25.0.4.1 (LTS) | | Database | H2 in-memory | | Transaction manager | `JpaTransactionManager` (Spring Boot's default for JPA) | ## Quickstart ```bash export JAVA_HOME=/path/to/jdk-25 mvn -DskipTests package ./scripts/run-all.sh # regenerate every transcript in docs/output/ mvn test # 19 contract tests ``` The application listens on **8081** so it can run alongside the other demos. ## Endpoints | Endpoint | Purpose | |---|---| | `GET /tx/propagation` | all seven propagations, called with and without a caller's transaction | | `GET /tx/rollback` | does the inner write survive the caller's rollback? | | `GET /tx/silent` | the six failures, plus a control that works | | `GET /tx/nested-jdbc` | `NESTED` succeeding, on a JDBC transaction manager | | `GET /tx/isolation` | isolation and `readOnly` where they apply and where they are ignored | ## Options | Property | Effect | |---|---| | `--demo.nested-allowed=true` | replaces the transaction manager with one that has `nestedTransactionAllowed=true`, to show the *second* failure | ## Documentation 1. [What `@Transactional` actually does](docs/01-what-transactional-does.md) 2. [The seven propagation values](docs/02-propagation.md) 3. [Rollback, and the exception that appears from nowhere](docs/03-rollback.md) 4. [`NESTED`, and why it does not work with JPA](docs/04-nested-and-jpa.md) 5. [Six ways `@Transactional` silently does nothing](docs/05-six-silent-failures.md) 6. [Isolation, read-only, and settings that are ignored](docs/06-isolation-and-readonly.md) ## Captured output | File | Produced by | |---|---| | [`00-versions.txt`](docs/output/00-versions.txt) | `scripts/demo-versions.sh` | | [`01-propagation.txt`](docs/output/01-propagation.txt) | `scripts/demo-propagation.sh` | | [`02-rollback.txt`](docs/output/02-rollback.txt) | `scripts/demo-rollback.sh` | | [`03-nested.txt`](docs/output/03-nested.txt) | `scripts/demo-nested.sh` | | [`04-silent-failures.txt`](docs/output/04-silent-failures.txt) | `scripts/demo-silent.sh` | | [`05-isolation.txt`](docs/output/05-isolation.txt) | `scripts/demo-isolation.sh` | ## Three things the transcripts settle - **`Propagation.NESTED` cannot be used with `JpaTransactionManager`.** It fails twice, with two different messages, the second of which blames your JPA provider. It works on `DataSourceTransactionManager`, because a savepoint is a JDBC concept. - **Catching a `REQUIRED` inner failure does not save the transaction.** The inner scope already marked it rollback-only, so the commit throws `UnexpectedRollbackException` from a place with no connection to the original cause. - **A checked exception commits.** So does a swallowed one. These two do not merely fail to start a transaction — they commit work the code was trying to abandon.