Zum Hauptinhalt springen

English + German

English stays on the page. Click the button to show the German text under each question.

Security, Testing, and AOP

These topics decide whether you can ship a service: who is the user, what test proves it, and why a proxy silently did nothing.

Deep chapters: Week 6, Week 7, Week 8 AOP.

Deutsch

Security, Testing und AOP

Diese Themen entscheiden, ob du einen Service shippen kannst: wer der User ist, welcher Test das belegt, und warum ein Proxy still nichts getan hat.

Vertiefung: Woche 6, Woche 7, Woche 8 AOP.

1. Authentication vs authorization? Where does each run?

Deutsch

Authentication vs Authorization? Wo läuft jeweils was?

Level: Junior · Listen for: who you are vs what you can do; filter chain

Niveau: Junior · Darauf hören sie: wer du bist vs was du darfst; filter chain

Model answer

Authentication establishes identity (session, JWT, basic). Authorization decides access given that identity (roles, permissions, method rules).

In Spring Security both happen in the filter chain, before MVC. A 401 means we do not know who you are. A 403 means we know, and you still cannot.

SecurityContext holds the Authentication for the rest of the request (thread-local by default).

Follow-ups

  • Anonymous vs unauthenticated?
  • Why is 401 vs 403 a product decision for hidden resources?

Trap:@PreAuthorize is authentication.”

Memory sentence: Authn is identity; authz is permission; both usually run in filters.

Study: Security mental model

Musterantwort

Authentication weist die Identität nach (Session, JWT, Basic). Authorization entscheidet den Zugriff auf Basis dieser Identität (Roles, Permissions, Method Rules).

In Spring Security passiert beides in der filter chain, vor MVC. 401 heißt: wir wissen nicht, wer du bist. 403 heißt: wir wissen es — und du darfst trotzdem nicht.

SecurityContext hält die Authentication für den Rest des Requests (standardmäßig thread-local).

Nachfragen

  • Anonymous vs unauthenticated?
  • Warum ist 401 vs 403 eine Produktentscheidung bei versteckten Ressourcen?

Falle:@PreAuthorize ist Authentication.“

Merksatz: Authn ist Identität; Authz ist Berechtigung; beides läuft meist in Filtern.

Lesen: Security Mental Model

2. Walk through the Security filter chain.

Deutsch

Geh die Security filter chain durch.

Level: Mid · Listen for: ordered filters; SecurityContextHolder; not DispatcherServlet

Niveau: Mid · Darauf hören sie: geordnete Filter; SecurityContextHolder; nicht DispatcherServlet

Model answer

Security is a FilterChainProxy with ordered filters: SecurityContextPersistence/Holder filter, CORS, CSRF, logout, username/password or JWT, exception translation, authorization (AuthorizationFilter).

If a filter rejects, DispatcherServlet may never run. That is why “my controller advice does not catch 403” is common — it is not an MVC exception yet, unless you configure AuthenticationEntryPoint / AccessDeniedHandler.

I debug with logging.level.org.springframework.security=DEBUG and the filter list, not by adding prints in the controller.

Follow-ups

  • Multiple SecurityFilterChain beans?
  • oncePerRequest?

Trap: putting security only in the controller as if (user == null).

Memory sentence: Security wraps the servlet; MVC may never see a rejected request.

Study: Security mental model

Musterantwort

Security ist ein FilterChainProxy mit geordneten Filtern: SecurityContextPersistence/Holder-Filter, CORS, CSRF, Logout, Username/Password oder JWT, Exception Translation, Authorization (AuthorizationFilter).

Wenn ein Filter ablehnt, läuft DispatcherServlet vielleicht nie. Deshalb ist „mein Controller Advice fängt 403 nicht“ häufig — das ist noch keine MVC-Exception, außer du konfigurierst AuthenticationEntryPoint / AccessDeniedHandler.

Ich debugge mit logging.level.org.springframework.security=DEBUG und der Filterliste, nicht mit Ausgaben im Controller.

Nachfragen

  • Mehrere SecurityFilterChain-Beans?
  • oncePerRequest?

Falle: Security nur im Controller als if (user == null).

Merksatz: Security umhüllt das Servlet; MVC sieht einen abgelehnten Request vielleicht nie.

Lesen: Security Mental Model

3. Password storage?

Deutsch

Passwortspeicherung?

Level: Mid · Listen for: adaptive hashing, not SHA-256; PasswordEncoder

Niveau: Mid · Darauf hören sie: adaptive Hashing, nicht SHA-256; PasswordEncoder

Model answer

I never store raw or reversible passwords. I use a PasswordEncoder: BCrypt/Argon2/SCrypt via DelegatingPasswordEncoder ({bcrypt}... prefix). SHA-256 is fast — that is a defect for passwords.

Boot’s default user is only for demos. Production: a UserDetailsService (or a JWT resource server) and a real encoder bean.

Follow-ups

  • Why a cost factor / work factor?
  • How do you migrate from SHA to BCrypt? (delegating encoder + on-login upgrade)

Trap: “we encrypt passwords with AES.” Encryption is reversible.

Memory sentence: Hash with an adaptive encoder; never encrypt passwords.

Study: Users and passwords

Musterantwort

Ich speichere nie Klartext und nie reversible Passwörter. Ich nutze einen PasswordEncoder: BCrypt/Argon2/SCrypt über DelegatingPasswordEncoder ({bcrypt}...-Prefix). SHA-256 ist schnell — bei Passwörtern ist das ein Fehler.

Der Default-User von Boot ist nur für Demos. Produktion: ein UserDetailsService (oder ein JWT Resource Server) und ein echter Encoder-Bean.

Nachfragen

  • Warum ein Cost Factor / Work Factor?
  • Wie migrierst du von SHA nach BCrypt? (Delegating Encoder + Upgrade beim Login)

Falle: „wir verschlüsseln Passwörter mit AES.“ Verschlüsselung ist reversibel.

Merksatz: Adaptiv hashen; Passwörter nie verschlüsseln.

Lesen: User und Passwörter

4. CSRF: when do you need it? When do you disable it?

Deutsch

CSRF: wann brauchst du es? Wann schaltest du es ab?

Level: Mid · Listen for: cookie-based browser sessions vs bearer tokens

Niveau: Mid · Darauf hören sie: cookie-basierte Browser-Sessions vs Bearer Tokens

Model answer

CSRF matters when the browser automatically sends credentials (session cookie) on cross-site requests. A hostile page can POST to your API with the user’s cookie. Spring Security enables CSRF by default for that world; you send a token on mutating requests.

For a stateless API with Authorization: Bearer (the browser does not auto-attach that header), CSRF is usually disabled, and you still need CORS configured correctly.

I do not disable CSRF on a server-rendered form app “because it is an API too”.

Follow-ups

  • SameSite cookies vs CSRF tokens?
  • SPA + cookie session: do you still need CSRF? (yes)

Trap: csrf.disable() copied from a JWT tutorial into a session app.

Memory sentence: CSRF protects cookie sessions from hostile sites; bearer tokens are not auto-sent.

Study: CSRF, CORS, JWT

Musterantwort

CSRF ist relevant, wenn der Browser Credentials automatisch mitschickt (Session-Cookie) bei Cross-Site-Requests. Eine feindliche Seite kann per POST auf deine API zugreifen — mit dem Cookie des Users. Spring Security aktiviert CSRF dafür standardmäßig; du schickst ein Token bei mutierenden Requests.

Für eine stateless API mit Authorization: Bearer (der Browser hängt den Header nicht von allein an) ist CSRF meist disabled, und CORS muss trotzdem richtig stehen.

Ich schalte CSRF bei einer serverseitig gerenderten Formular-App nicht ab, „weil das auch eine API ist“.

Nachfragen

  • SameSite-Cookies vs CSRF-Tokens?
  • SPA + Cookie-Session: brauchst du trotzdem CSRF? (ja)

Falle: csrf.disable() aus einem JWT-Tutorial in eine Session-App kopiert.

Merksatz: CSRF schützt Cookie-Sessions vor feindlichen Seiten; Bearer Tokens werden nicht automatisch gesendet.

Lesen: CSRF, CORS, JWT

5. Session vs JWT for an API?

Deutsch

Session vs JWT für eine API?

Level: Mid · Listen for: revocation, size, where state lives

Niveau: Mid · Darauf hören sie: Revocation, Größe, wo der State lebt

Model answer

Session (stateful): server stores the session; cookie is an id. Logout is real. Easy to revoke. Needs sticky sessions or shared session store.

JWT (stateless): the token is the session claims. Easy to scale; logout and “user fired” are hard (short TTL + blocklist, or opaque tokens + introspection). Tokens can leak via localStorage XSS; prefer httpOnly cookies if the client is a browser, and then you are back to CSRF.

I pick sessions for first-party browsers, JWT/opaque tokens for service-to-service and mobile, and I never store sensitive data in JWT claims.

Follow-ups

  • Refresh tokens?
  • Why is a 24h JWT a security incident waiting?

Trap: “JWT is more secure than sessions.”

Memory sentence: Sessions revoke easily; JWTs scale easily; security is in the whole design.

Study: CSRF, CORS, JWT

Musterantwort

Session (stateful): der Server speichert die Session; das Cookie ist eine Id. Logout ist echt. Revocation ist einfach. Braucht Sticky Sessions oder einen geteilten Session-Store.

JWT (stateless): das Token ist die Session-Claims. Leicht zu skalieren; Logout und „User gekündigt“ sind schwer (kurze TTL + Blocklist, oder opaque Tokens + Introspection). Tokens können über localStorage-XSS leaken; bei einem Browser-Client lieber httpOnly-Cookies — und dann bist du wieder bei CSRF.

Ich nehme Sessions für First-Party-Browser, JWT/opaque Tokens für Service-to-Service und Mobile, und ich lege nie sensible Daten in JWT-Claims.

Nachfragen

  • Refresh Tokens?
  • Warum ist ein 24h-JWT ein Sicherheitsvorfall, der nur noch wartet?

Falle: „JWT ist sicherer als Sessions.“

Merksatz: Sessions lassen sich leicht ungültig machen; JWTs skalieren leicht; Security steckt im Gesamtdesign.

Lesen: CSRF, CORS, JWT

6. URL security vs method security?

Deutsch

URL-Security vs Method Security?

Level: Mid · Listen for: both layers; default deny

Niveau: Mid · Darauf hören sie: beide Schichten; Default Deny

Model answer

authorizeHttpRequests protects paths. @PreAuthorize / @Secured protect methods on beans (AOP). I use path rules as the coarse filter (/admin/**) and method security for domain rules (#order.customerId == principal.id).

Method security is a proxy: self-invocation skips it, same as transactions. Controllers are proxied if you enable method security on them, but I prefer rules on the service so all entry points (MVC, events, @Async) are covered.

Default: deny what I did not mention. anyRequest().authenticated() is a baseline, not a complete policy.

Follow-ups

  • hasRole('ADMIN') vs hasAuthority('ROLE_ADMIN')?
  • SpEL in @PreAuthorize going to the database?

Trap: only path matchers, and a second mapping (/internal) that forgot the rule.

Memory sentence: Paths are the fence; method security is the invariant.

Study: Authorization

Musterantwort

authorizeHttpRequests schützt Pfade. @PreAuthorize / @Secured schützen Methoden auf Beans (AOP). Ich nutze Path-Rules als groben Filter (/admin/**) und Method Security für Domain-Regeln (#order.customerId == principal.id).

Method Security ist ein Proxy: Self-Invocation überspringt sie, wie bei Transaktionen. Controller bekommen einen Proxy, wenn du Method Security auf ihnen aktivierst, aber ich lege die Regeln lieber auf den Service, damit alle Einstiege (MVC, Events, @Async) abgedeckt sind.

Default: deny für alles, was ich nicht genannt habe. anyRequest().authenticated() ist eine Baseline, keine vollständige Policy.

Nachfragen

  • hasRole('ADMIN') vs hasAuthority('ROLE_ADMIN')?
  • SpEL in @PreAuthorize, das in die Datenbank geht?

Falle: nur Path-Matcher, und ein zweites Mapping (/internal), das die Regel vergessen hat.

Merksatz: Pfade sind der Zaun; Method Security ist die Invariante.

Lesen: Authorization

7. How do you test security?

Deutsch

Wie testest du Security?

Level: Mid · Listen for: spring-security-test, not “we’ll test in staging”

Niveau: Mid · Darauf hören sie: spring-security-test, nicht „testen wir in Staging“

Model answer

I write MVC tests with spring-security-test: @WithMockUser, csrf(), jwt(), and I assert 401/403/200 for the same URL.

@WebMvcTest + Security config shows filter behavior. I still want one integration test that boots Security with the real matcher list.

I never disable Security in test profile as a shortcut. That tests a different application.

Follow-ups

  • @WithMockUser(roles = "ADMIN") vs a real JWT decoder test?
  • Testing method security without MVC?

Trap: security.ignored=/** in application-test.yml.

Memory sentence: Security is part of the contract; tests must include 401 and 403.

Study: Security testing

Musterantwort

Ich schreibe MVC-Tests mit spring-security-test: @WithMockUser, csrf(), jwt(), und ich prüfe 401/403/200 für dieselbe URL.

@WebMvcTest + Security-Config zeigt Filter-Verhalten. Ich will trotzdem einen Integrationstest, der Security mit der echten Matcher-Liste bootet.

Ich schalte Security nie im test-Profil als Abkürzung ab. Das testet eine andere Anwendung.

Nachfragen

  • @WithMockUser(roles = "ADMIN") vs ein echter JWT-Decoder-Test?
  • Method Security ohne MVC testen?

Falle: security.ignored=/** in application-test.yml.

Merksatz: Security gehört zum Vertrag; Tests müssen 401 und 403 enthalten.

Lesen: Security Testing

8. @SpringBootTest vs @WebMvcTest vs @DataJpaTest vs @JsonTest?

Deutsch

@SpringBootTest vs @WebMvcTest vs @DataJpaTest vs @JsonTest?

Level: Mid · Listen for: slice vs full context; what is mocked

Niveau: Mid · Darauf hören sie: Slice vs voller Context; was gemockt ist

Model answer
SliceLoadsUse
Unit (plain JUnit + Mockito)nothingdomain/service with mocks
@WebMvcTestMVC + one controllerHTTP contract, validation, advice
@DataJpaTestJPA + repos + embedded DBqueries, constraints
@JsonTestJacksonDTO serialization
@SpringBootTestalmost everythingwiring, real slices together
@SpringBootTest + MockMvc / TestRestTemplate / WebTestClientfull HTTPend-to-end-ish

Slices are fast and precise. Full tests are slow and catch wiring. I do not @SpringBootTest every class.

Follow-ups

  • @MockBean vs @MockitoBean (Boot 3.4+) vs constructor injection in unit tests?
  • Why can @WebMvcTest miss a Security filter you forgot to import?

Trap: one @SpringBootTest to rule them all.

Memory sentence: Pick the smallest test that can fail for the bug you care about.

Study: Testing mental model

Musterantwort

SliceLädtWofür
Unit (reines JUnit + Mockito)nichtsDomain/Service mit Mocks
@WebMvcTestMVC + ein ControllerHTTP-Vertrag, Validierung, Advice
@DataJpaTestJPA + Repos + Embedded DBQueries, Constraints
@JsonTestJacksonDTO-Serialisierung
@SpringBootTestfast allesWiring, echte Slices zusammen
@SpringBootTest + MockMvc / TestRestTemplate / WebTestClientvolles HTTPEnd-to-End-mäßig

Slices sind schnell und präzise. Volle Tests sind langsam und fangen Wiring. Ich starte nicht für jede Klasse ein @SpringBootTest.

Nachfragen

  • @MockBean vs @MockitoBean (Boot 3.4+) vs Konstruktor-Injection in Unit-Tests?
  • Warum kann @WebMvcTest einen Security-Filter verpassen, den du vergessen hast zu importieren?

Falle: ein @SpringBootTest, um sie alle zu knechten.

Merksatz: Nimm den kleinsten Test, der für den Bug fehlschlagen kann, der dich interessiert.

Lesen: Testing Mental Model

9. How do you unit-test a service?

Deutsch

Wie unit-testest du einen Service?

Level: Junior · Listen for: no Spring, constructor mocks, AssertJ

Niveau: Junior · Darauf hören sie: kein Spring, Konstruktor-Mocks, AssertJ

Model answer

If the service uses constructor injection, I new OrderService(mockRepo, mockClock) in a plain test. Mockito stubs the repo; AssertJ reads the result. No @SpringBootTest.

I test behavior: given this order, when I cancel, then status is cancelled and save is called. I do not test getters.

If the test needs @ExtendWith(MockitoExtension.class) and 15 mocks, the service is doing too much.

Follow-ups

  • verifyNoMoreInteractions — when is it noise?
  • Time: Clock vs LocalDateTime.now()?

Trap: starting the whole context to test an if.

Memory sentence: A unit-testable service is a constructor plus mocks, not a running container.

Study: Unit testing services

Musterantwort

Wenn der Service Konstruktor-Injection nutzt, mache ich new OrderService(mockRepo, mockClock) in einem reinen Test. Mockito stubbt das Repo; AssertJ liest das Ergebnis. Kein @SpringBootTest.

Ich teste Verhalten: gegeben diese Order, wenn ich storniere, dann ist der Status storniert und save wird aufgerufen. Ich teste keine Getter.

Wenn der Test @ExtendWith(MockitoExtension.class) und 15 Mocks braucht, macht der Service zu viel.

Nachfragen

  • verifyNoMoreInteractions — wann ist das nur Rauschen?
  • Zeit: Clock vs LocalDateTime.now()?

Falle: den ganzen Context starten, um ein if zu testen.

Merksatz: Ein unit-testbarer Service ist ein Konstruktor plus Mocks, kein laufender Container.

Lesen: Services unit-testen

10. What does @MockBean actually replace?

Deutsch

Was ersetzt @MockBean wirklich?

Level: Mid · Listen for: a bean in the context; not a unit-test mock

Niveau: Mid · Darauf hören sie: ein Bean im Context; kein Unit-Test-Mock

Model answer

@MockBean (or @MockitoBean) puts a Mockito mock into the Spring context, replacing the real bean. Use it in slices: @WebMvcTest + mock the service.

It is not a substitute for unit tests. It also can surprise you: every class that injects that type now gets the mock, and you may hide the wiring bug you wanted to catch.

Prefer constructor unit tests for services. Prefer @MockBean at the boundary of a slice.

Follow-ups

  • @SpyBean risks?
  • Replacing a @Transactional bean with a mock — what is no longer tested?

Trap: @SpringBootTest + @MockBean on everything, then claiming it is an integration test.

Memory sentence: @MockBean fakes a collaborator inside the context; it is still a Spring test.

Study: Web layer testing

Musterantwort

@MockBean (oder @MockitoBean) legt ein Mockito-Mock in den Spring Context und ersetzt den echten Bean. Typischer Einsatz in Slices: @WebMvcTest + den Service mocken.

Das ersetzt keine Unit-Tests. Es kann dich auch überraschen: jede Klasse, die diesen Typ injiziert, bekommt jetzt das Mock — und du versteckst vielleicht den Wiring-Bug, den du fangen wolltest.

Für Services lieber Unit-Tests über den Konstruktor. @MockBean an der Grenze eines Slice.

Nachfragen

  • Risiken von @SpyBean?
  • Einen @Transactional-Bean durch ein Mock ersetzen — was wird dann nicht mehr getestet?

Falle: @SpringBootTest + @MockBean auf alles, und dann behaupten, das sei ein Integrationstest.

Merksatz: @MockBean täuscht eine Abhängigkeit im Context vor; es bleibt ein Spring-Test.

Lesen: Web-Layer-Tests

11. JDK dynamic proxy vs CGLIB? Why does final break Spring?

Deutsch

JDK Dynamic Proxy vs CGLIB? Warum macht final Spring kaputt?

Level: Mid · Listen for: interface proxy vs subclass; self-invocation both ways

Niveau: Mid · Darauf hören sie: Interface-Proxy vs Subclass; Self-Invocation in beiden Fällen

Model answer

If the bean implements an interface, Spring may create a JDK proxy that implements the same interfaces. Calls through the interface hit the proxy. Casts to the concrete class fail.

Otherwise Spring uses CGLIB (or ByteBuddy) to subclass the concrete type. final classes/methods cannot be subclassed/overridden, so @Transactional / @Async / @PreAuthorize may silently not apply (or fail at startup, depending on version and settings).

Self-invocation bypasses both proxy types. @EnableAspectJAutoProxy(exposeProxy = true) + AopContext.currentProxy() is a workaround I avoid; I extract a second bean instead.

Follow-ups

  • spring.aop.proxy-target-class=true default in Boot?
  • Why is @Transactional on a private method ignored?

Trap: “CGLIB is faster so we always use it.”

Memory sentence: Spring features that “need a proxy” cannot intercept this or final methods.

Study: AOP mental model

Musterantwort

Wenn der Bean ein Interface implementiert, kann Spring einen JDK proxy bauen, der dieselben Interfaces implementiert. Aufrufe über das Interface treffen den Proxy. Casts auf die konkrete Klasse scheitern.

Sonst nutzt Spring CGLIB (oder ByteBuddy), um den konkreten Typ zu subclassen. final Klassen/Methoden kann man nicht subclassen/überschreiben, also greifen @Transactional / @Async / @PreAuthorize still nicht (oder der Start scheitert, je nach Version und Einstellungen).

Self-Invocation umgeht beide Proxy-Typen. @EnableAspectJAutoProxy(exposeProxy = true) + AopContext.currentProxy() ist ein Workaround, den ich meide; ich ziehe stattdessen einen zweiten Bean raus.

Nachfragen

  • spring.aop.proxy-target-class=true Default in Boot?
  • Warum wird @Transactional auf einer private Methode ignoriert?

Falle: „CGLIB ist schneller, also nutzen wir es immer.“

Merksatz: Spring-Features, die „einen Proxy brauchen“, können this oder final-Methoden nicht abfangen.

Lesen: AOP Mental Model

12. Advice types? When is @Around the wrong default?

Deutsch

Advice-Typen? Wann ist @Around der falsche Default?

Level: Mid · Listen for: @Before/@After/@AfterReturning/@AfterThrowing/@Around

Niveau: Mid · Darauf hören sie: @Before/@After/@AfterReturning/@AfterThrowing/@Around

Model answer

@Around can do everything, and that is why it is dangerous: you can forget joinPoint.proceed(), swallow exceptions, or break return types.

I use the narrowest advice: @AfterThrowing for metrics on errors, @AfterReturning for audit of results, @Around only when I must time a call or control proceed.

Pointcuts should be explicit (execution on a package / annotation), not * across the app.

Follow-ups

  • Ordering multiple aspects?
  • Why can aspects on repositories hide SQL exceptions?

Trap: a global @Around that logs arguments including passwords.

Memory sentence: Narrow advice; proceed is a loaded gun.

Study: AOP mental model

Musterantwort

@Around kann alles, und genau deshalb ist es gefährlich: du kannst joinPoint.proceed() vergessen, Exceptions schlucken oder Rückgabetypen kaputtmachen.

Ich nehme den engsten Advice: @AfterThrowing für Metriken bei Fehlern, @AfterReturning für Audit der Ergebnisse, @Around nur wenn ich einen Aufruf messen oder proceed steuern muss.

Pointcuts sollen explizit sein (execution auf Package / Annotation), nicht * über die ganze App.

Nachfragen

  • Reihenfolge mehrerer Aspects?
  • Warum können Aspects auf Repositories SQL-Exceptions verstecken?

Falle: ein globaler @Around, der Argumente loggt — inklusive Passwörter.

Merksatz: Enger Advice; proceed ist eine geladene Waffe.

Lesen: AOP Mental Model

13. @Async pitfalls?

Deutsch

Fallen bei @Async?

Level: Mid · Listen for: proxy, executor, exceptions, transaction boundary

Niveau: Mid · Darauf hören sie: Proxy, Executor, Exceptions, Transaktionsgrenze

Model answer

@Async is a proxy that submits the method to an executor. Traps:

  • self-invocation → runs synchronously
  • default executor may be unbounded or the simple one
  • exceptions in void methods go to an AsyncUncaughtExceptionHandler, not the caller
  • the transaction of the caller does not include the async method; the async method needs its own @Transactional if it writes
  • SecurityContext / MDC may not propagate unless you wrap the executor

I use @Async for non-critical fan-out. For reliability I prefer a message queue.

Follow-ups

  • Return CompletableFuture vs void?
  • Virtual threads as the async executor?

Trap: @Async + @Transactional on the same method without saying which proxy is outer.

Memory sentence: Async is a different thread and a different transaction unless you design otherwise.

Study: Scheduling and async

Musterantwort

@Async ist ein Proxy, der die Methode an einen Executor übergibt. Fallen:

  • Self-Invocation → läuft synchron
  • Default-Executor kann unbegrenzt wachsen oder der Simple-Executor sein
  • Exceptions in void-Methoden gehen an einen AsyncUncaughtExceptionHandler, nicht an den Caller
  • die Transaktion des Callers umfasst die async-Methode nicht; die async-Methode braucht eigenes @Transactional, wenn sie schreibt
  • SecurityContext / MDC propagieren oft nicht, außer du den Executor wrappst

Ich nutze @Async für unkritisches Fan-out. Für Zuverlässigkeit nehme ich lieber eine Message Queue.

Nachfragen

  • CompletableFuture zurückgeben vs void?
  • Virtual Threads als Async-Executor?

Falle: @Async + @Transactional auf derselben Methode, ohne zu sagen, welcher Proxy außen liegt.

Merksatz: Async ist ein anderer Thread und eine andere Transaktion, außer du schneidest es anders.

Lesen: Scheduling und Async

14. Spring events: transactional vs not?

Deutsch

Spring Events: transaktional oder nicht?

Level: Mid · Listen for: ApplicationEventPublisher; @TransactionalEventListener

Niveau: Mid · Darauf hören sie: ApplicationEventPublisher; @TransactionalEventListener

Model answer

ApplicationEventPublisher.publishEvent is in-process, not Kafka. Listeners run in the same JVM. By default they run synchronously in the publishing thread.

@TransactionalEventListener(phase = AFTER_COMMIT) runs after the transaction commits — that is the right hook for “send email after order is saved”. BEFORE_COMMIT / AFTER_ROLLBACK exist too. If you listen without this, the listener can see uncommitted rows or send an email for a rolled-back order.

For other services, use a broker. Spring events are not a network protocol.

Follow-ups

  • @Async listeners?
  • Event classes as records?

Trap: publishing an event and assuming another microservice received it.

Memory sentence: Spring events are in-process; after-commit listeners belong with transactions.

Study: Spring events

Musterantwort

ApplicationEventPublisher.publishEvent ist in-process, nicht Kafka. Listener laufen in derselben JVM. Standardmäßig laufen sie synchron im Thread, der veröffentlicht.

@TransactionalEventListener(phase = AFTER_COMMIT) läuft nach dem Commit der Transaktion — das ist der richtige Hook für „Mail schicken, nachdem die Order gespeichert ist“. BEFORE_COMMIT / AFTER_ROLLBACK gibt es auch. Lauscht du ohne diese Annotation, kann der Listener uncommitted Rows sehen oder eine Mail für eine zurückgerollte Order schicken.

Für andere Services nimm einen Broker. Spring Events sind kein Netzwerkprotokoll.

Nachfragen

  • @Async-Listener?
  • Event-Klassen als Records?

Falle: ein Event veröffentlichen und annehmen, ein anderer Microservice hätte es empfangen.

Merksatz: Spring Events sind in-process; After-Commit-Listener gehören zu Transaktionen.

Lesen: Spring Events

15. How do you test @Transactional rollback?

Deutsch

Wie testest du @Transactional-Rollback?

Level: Mid · Listen for: @DataJpaTest / @SpringBootTest + @Transactional on the test; @Rollback / @Commit

Niveau: Mid · Darauf hören sie: @DataJpaTest / @SpringBootTest + @Transactional auf dem Test; @Rollback / @Commit

Model answer

@DataJpaTest is transactional by default and rolls back at the end of each test, so the database stays clean. That is great, but it can hide REQUIRES_NEW and commit-aware listeners (they see a transaction that never commits in production’s way).

If I need to assert after-commit behavior, I use @Transactional(propagation = NOT_SUPPORTED) on that test, or @Commit, and I clean data myself.

I also test rollback of the service: throw, then assert the row is absent — in a test that actually commits the inner work.

Follow-ups

  • Why did my @TransactionalEventListener never fire in tests?
  • Testcontainers vs H2?

Trap: trusting rollback tests alone for AFTER_COMMIT listeners.

Memory sentence: Test rollback is not the same as a production commit; know which one you are simulating.

Study: Integration testing

Musterantwort

@DataJpaTest ist standardmäßig transaktional und rollt am Ende jedes Tests zurück, damit die Datenbank sauber bleibt. Das ist super, kann aber REQUIRES_NEW und Listener, die auf den Commit hören, verstecken (die sehen eine Transaktion, die nie so committet wie in Produktion).

Wenn ich After-Commit-Verhalten prüfen muss, setze ich @Transactional(propagation = NOT_SUPPORTED) auf diesen Test, oder @Commit, und räume die Daten selbst auf.

Ich teste auch das Rollback des Services: Exception werfen, dann prüfen, dass die Row fehlt — in einem Test, der die innere Arbeit wirklich committet.

Nachfragen

  • Warum ist mein @TransactionalEventListener in Tests nie ausgelöst worden?
  • Testcontainers vs H2?

Falle: Rollback-Tests allein für AFTER_COMMIT-Listener vertrauen.

Merksatz: Test-Rollback ist nicht dasselbe wie ein Commit in Produktion; du musst wissen, welches du simulierst.

Lesen: Integrationstests

16. What would you test for a POST /orders endpoint?

Deutsch

Was würdest du für einen POST /orders-Endpunkt testen?

Level: Senior · Listen for: a test pyramid for one feature, not a tool list

Niveau: Senior · Darauf hören sie: eine Testpyramide für ein Feature, keine Tool-Liste

Model answer

For one endpoint I want:

  1. Service unit tests — price, stock, invariant (cannot order empty cart)
  2. @WebMvcTest — 400 on invalid JSON, 401 without token, 201 + Location on success, service mocked
  3. @DataJpaTest — unique constraint, query that loads the graph
  4. One @SpringBootTest — security + HTTP + DB (Testcontainers), happy path and one authorization failure

I do not duplicate all cases at every level. I push business rules down, HTTP mapping to the slice, and keep a thin integration net.

Follow-ups

  • Contract tests with the frontend?
  • How do you test idempotency keys?

Trap: 40 @SpringBootTests that all insert the same fixture through HTTP.

Memory sentence: Prove the rule in a unit test; prove the HTTP and security in a slice; prove wiring once.

Study: Testing mental model

Musterantwort

Für einen Endpunkt will ich:

  1. Service-Unit-Tests — Preis, Bestand, Invariante (leerer Warenkorb nicht bestellbar)
  2. @WebMvcTest — 400 bei ungültigem JSON, 401 ohne Token, 201 + Location bei Erfolg, Service gemockt
  3. @DataJpaTest — Unique Constraint, Query, die den Graph lädt
  4. Ein @SpringBootTest — Security + HTTP + DB (Testcontainers), Happy Path und ein Authorization-Fehler

Ich dupliziere nicht alle Fälle auf jeder Ebene. Fachregeln nach unten, HTTP-Mapping in den Slice, und ein dünnes Netz aus Integrationstests.

Nachfragen

  • Contract-Tests mit dem Frontend?
  • Wie testest du Idempotency Keys?

Falle: 40 @SpringBootTests, die alle dasselbe Fixture über HTTP einfügen.

Merksatz: Beweise die Regel im Unit-Test; HTTP und Security im Slice; Wiring einmal.

Lesen: Testing Mental Model