Zum Hauptinhalt springen

Woche 8, Tag 3 — Scheduling und Async

Ziel

Heute lernst du Scheduling und asynchrone Ausführung in Spring.

Die Kernfragen:

  1. Was ist Scheduling?
  2. Was ist @Scheduled?
  3. Was ist @EnableScheduling?
  4. Was ist fixed rate?
  5. Was ist fixed delay?
  6. Was ist Cron?
  7. Was ist @Async?
  8. Was ist @EnableAsync?
  9. Was ist ein Executor?
  10. Was ist ein Thread-Pool?
  11. Was bedeutet async wirklich?
  12. Was sind häufige Produktionsfallen?
  13. Was ist wichtig für die Spring-Professional-Prüfung?

1. Kurz-Wiederholung aus Woche 8, Tag 2

In Tag 2 hast du gelernt:

  • Spring Events signalisieren, dass etwas passiert ist.
  • ApplicationEventPublisher veröffentlicht Events.
  • @EventListener empfängt Events.
  • Events sind standardmäßig synchron.
  • @TransactionalEventListener(AFTER_COMMIT) läuft nach erfolgreichem Commit.
  • @Async kann Event-Listener asynchron machen.
  • Spring Events sind in-process, kein dauerhaftes Messaging.
  • Nutze Kafka/RabbitMQ/Outbox für dauerhaftes verteiltes Messaging.

Merksatz:


Spring Events sind lokale In-Process-Events, kein dauerhaftes Messaging.

Heute lernst du zwei verwandte Themen:


geplante Ausführung (scheduled execution)
asynchrone Ausführung (asynchronous execution)

2. Überblick

Spring unterstützt zwei gängige Arten von Hintergrundausführung.

Scheduling

Etwas zu einer geplanten Zeit oder in einem Intervall ausführen.

Beispiele:


alle 5 Minuten ausführen
jede Nacht um 02:00 Uhr ausführen
jeden Montagmorgen ausführen
abgelaufene Tokens jede Stunde bereinigen
täglichen Report jeden Tag senden

Spring-Annotation:


@Scheduled

Async

Etwas in einem anderen Thread ausführen.

Beispiele:


E-Mail im Hintergrund senden
PDF im Hintergrund erzeugen
externe API aufrufen, ohne den Aufrufer zu blockieren
Benachrichtigung asynchron verarbeiten

Spring-Annotation:


@Async

Merksatz:


@Scheduled = wann soll es laufen?
@Async = soll es in einem anderen Thread laufen?

3. Scheduling vs. Async

ThemaSchedulingAsync
KernfrageWann soll die Aufgabe laufen?In welchem Thread soll die Methode laufen?
Annotation@Scheduled@Async
Aktivieren mit@EnableScheduling@EnableAsync
Typischer Einsatzperiodische JobsHintergrundausführung
Beispielabgelaufene Tokens jede Stunde bereinigenE-Mail in einem anderen Thread senden

Merksatz:

Scheduling dreht sich um Zeit. Async dreht sich um Threads.


4. Was ist @Scheduled?

@Scheduled sagt Spring:


Führe diese Methode automatisch nach einem Zeitplan aus.

Beispiel:


@Component
public class TokenCleanupJob {

@Scheduled(fixedDelay = 60_000)
public void cleanExpiredTokens() {
System.out.println("Cleaning expired tokens...");
}
}

Das läuft wiederholt mit einer festen Verzögerung (fixed delay).

Wichtig:


Die Methode sollte üblicherweise void zurückgeben.
Die Methode sollte keine Argumente erwarten.
Spring ruft sie automatisch auf.

Merksatz:

@Scheduled lässt Spring eine Methode periodisch ausführen.


5. Scheduling aktivieren

Um @Scheduled zu nutzen, aktivierst du Scheduling:


@Configuration
@EnableScheduling
public class SchedulingConfig {
}

In einer Spring-Boot-App kann diese Konfiguration auf jeder Configuration-Klasse stehen.

Beispiel:


@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

Merksatz:

@EnableScheduling aktiviert die Verarbeitung geplanter Methoden.


6. Einfacher Scheduled Job


@Component
public class DailyReportJob {

@Scheduled(fixedRate = 10_000)
public void generateReport() {
System.out.println("Generating report...");
}
}

Das bedeutet:


Führe diese Methode alle 10 Sekunden aus.

Das genaue Verhalten hängt aber von fixed rate vs. fixed delay ab.

Merksatz:

Ein Scheduled Job ist eine Spring-Bean-Methode, die automatisch ausgeführt wird.


7. Fixed Rate

fixedRate bedeutet:


Starte eine neue Ausführung in einem regelmäßigen Intervall, gemessen ab der Startzeit der vorherigen Ausführung.

Beispiel:


@Scheduled(fixedRate = 5_000)
public void runEveryFiveSeconds() {
System.out.println("Running...");
}

Zeitachse:


00:00 Start
00:05 Start
00:10 Start
00:15 Start

Wichtig:


fixedRate misst Start-zu-Start.

Merksatz:

Fixed rate bedeutet: alle X Millisekunden neu starten.


8. Fixed-Rate-Falle

Stell dir vor:


@Scheduled(fixedRate = 5_000)
public void longJob() {
// dauert 8 Sekunden
}

Frage:


Was passiert, wenn der Job länger dauert als die fixed rate?

Antwort:


Das hängt von der Scheduler-/Thread-Pool-Konfiguration ab.
Mit einem einzelnen Scheduler-Thread laufen Ausführungen nicht wirklich parallel.
Mit einem Pool kann es bei entsprechender Konfiguration zu Überlappungen kommen.

Produktionswarnung:


Nutze fixedRate nicht blind für lang laufende Jobs.

Merksatz:

Fixed rate kann gefährlich sein, wenn der Job länger dauert als das Intervall.


9. Fixed Delay

fixedDelay bedeutet:


Warte X Millisekunden, nachdem die vorherige Ausführung beendet ist.

Beispiel:


@Scheduled(fixedDelay = 5_000)
public void runWithDelay() {
System.out.println("Running...");
}

Zeitachse, wenn der Job 2 Sekunden dauert:


00:00 Start
00:02 Ende
00:07 Start
00:09 Ende
00:14 Start

Wichtig:


fixedDelay misst Ende-zu-nächster-Start.

Merksatz:

Fixed delay wartet, bis der vorherige Lauf beendet ist.


10. Fixed Rate vs. Fixed Delay

ThemafixedRatefixedDelay
Misst abStartzeitEndzeit
Bedeutungalle X ms ausführenX ms nach Abschluss warten
Am besten fürregelmäßiges Polling-RhythmusÜberlappung vermeiden / Pause nach der Arbeit
RisikoÜberlappung oder Druck bei langsamem JobZeitplan verschiebt sich bei langsamem Job

Merksatz:


fixedRate = Start zu Start.
fixedDelay = Ende zu nächstem Start.

11. Initial Delay

initialDelay bedeutet:


Vor der ersten Ausführung warten.

Beispiel:


@Scheduled(initialDelay = 10_000, fixedDelay = 60_000)
public void runAfterStartupDelay() {
System.out.println("First run after 10 seconds, then every 60 seconds after finish.");
}

Anwendungsfall:


der Anwendung Zeit zum Starten geben
Job nicht sofort ausführen
warten, bis Abhängigkeiten bereit sind

Merksatz:

initialDelay verzögert den ersten geplanten Lauf.


12. Time Unit

Modernes Spring unterstützt timeUnit.

Beispiel:


@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.MINUTES)
public void cleanExpiredSessions() {
}

Ohne timeUnit sind die Werte oft Millisekunden.

Beispiel:


@Scheduled(fixedDelay = 5000)

bedeutet:


5000 Millisekunden = 5 Sekunden

Merksatz:

Ohne klare Zeiteinheiten sind geplante Werte leicht falsch zu lesen.


13. Cron

Cron wird für kalenderbasierte Zeitpläne verwendet.

Beispiel:


@Scheduled(cron = "0 0 2 * * *")
public void runEveryNightAtTwo() {
System.out.println("Nightly job");
}

Bedeutung:


Jeden Tag um 02:00 Uhr ausführen.

Das Spring-Cron-Format hat üblicherweise 6 Felder:


Sekunde Minute Stunde Tag-des-Monats Monat Wochentag

Beispiel:


0 0 2 * * *

Bedeutet:


Sekunde = 0
Minute = 0
Stunde = 2
jeden Tag des Monats
jeden Monat
jeden Wochentag

Merksatz:

Cron ist für kalenderbasierte Zeitpläne gedacht.


14. Häufige Cron-Beispiele

Jeden Tag um 02:00 Uhr:


@Scheduled(cron = "0 0 2 * * *")

Jede Stunde:


@Scheduled(cron = "0 0 * * * *")

Jeden Montag um 08:00 Uhr:


@Scheduled(cron = "0 0 8 * * MON")

Alle 15 Minuten:


@Scheduled(cron = "0 */15 * * * *")

Merksatz:

Cron ist mächtig, aber leicht falsch zu lesen.


15. Cron-Zeitzone

Du kannst eine Zeitzone angeben:


@Scheduled(cron = "0 0 2 * * *", zone = "Europe/Berlin")
public void runAtBerlinTwoAm() {
}

Warum wichtig?


Server laufen vielleicht in UTC
der Geschäftszeitplan braucht vielleicht Europe/Berlin
Sommerzeit kann relevant sein

Merksatz:

Denke bei Cron-Jobs immer an die Zeitzone.


16. Regeln für geplante Methoden

Geplante Methoden sollten üblicherweise:


Methoden ohne Argumente sein
void zurückgeben oder der Rückgabewert wird ignoriert
auf von Spring verwalteten Beans liegen
nicht auf Request-Context angewiesen sein
Exceptions sorgfältig behandeln
nicht zu lange ohne Monitoring laufen

Schlecht:


@Scheduled(fixedDelay = 5000)
public String runJob(String input) {
return "done";
}

Besser:


@Scheduled(fixedDelay = 5000)
public void runJob() {
}

Merksatz:

Geplante Methoden werden von Spring aufgerufen, nicht von einem normalen Controller-Request.


17. Geplanter Bean muss von Spring verwaltet werden

Funktioniert:


@Component
public class CleanupJob {

@Scheduled(fixedDelay = 60_000)
public void clean() {
}
}

Funktioniert nicht:


CleanupJob job = new CleanupJob();

weil Spring dieses Objekt nicht verwaltet.

Merksatz:

@Scheduled funktioniert auf Spring Beans.


18. Exception-Falle bei Scheduled Jobs

Wenn eine geplante Methode eine Exception wirft:


@Scheduled(fixedDelay = 60_000)
public void syncExternalSystem() {
throw new RuntimeException("External system failed");
}

Problem:


Job-Ausführung schlägt fehl
Logs können den Fehler enthalten
zukünftiges Verhalten hängt vom Scheduler/Fehlerbehandlung ab
wichtige Arbeit kann übersprungen oder wiederholt fehlschlagen

Besser:


@Scheduled(fixedDelay = 60_000)
public void syncExternalSystem() {
try {
externalSyncService.sync();
} catch (Exception exception) {
log.error("External sync failed", exception);
}
}

Merksatz:

Scheduled Jobs sollten Exceptions sorgfältig behandeln und loggen.


19. Keine große Logik direkt in der Job-Klasse

Schlecht:


@Component
public class ReportJob {

@Scheduled(cron = "0 0 2 * * *")
public void generateReports() {
// 300 Zeilen Business-Logik
}
}

Besser:


@Component
public class ReportJob {

private final ReportService reportService;

public ReportJob(ReportService reportService) {
this.reportService = reportService;
}

@Scheduled(cron = "0 0 2 * * *")
public void generateReports() {
reportService.generateDailyReports();
}
}

Merksatz:

Scheduled-Job-Klassen sollten Services auslösen, nicht die gesamte Business-Logik enthalten.


20. Scheduling und Transaktionen

Eine geplante Methode kann einen transaktionalen Service aufrufen.

Gut:


@Component
public class TokenCleanupJob {

private final TokenCleanupService tokenCleanupService;

public TokenCleanupJob(TokenCleanupService tokenCleanupService) {
this.tokenCleanupService = tokenCleanupService;
}

@Scheduled(fixedDelay = 60_000)
public void cleanExpiredTokens() {
tokenCleanupService.cleanExpiredTokens();
}
}

Service:


@Service
public class TokenCleanupService {

private final TokenRepository tokenRepository;

public TokenCleanupService(TokenRepository tokenRepository) {
this.tokenRepository = tokenRepository;
}

@Transactional
public void cleanExpiredTokens() {
tokenRepository.deleteExpiredTokens(Instant.now());
}
}

Warum gut?


geplanter Bean ruft einen anderen Spring Bean auf
transaktionale Methode wird über den Proxy aufgerufen
Business-Logik ist getrennt

Merksatz:

Lass Scheduled Jobs transaktionale Services aufrufen.


21. Self-Invocation-Falle beim Scheduling

Schlecht:


@Component
public class CleanupJob {

@Scheduled(fixedDelay = 60_000)
public void run() {
cleanExpiredTokens();
}

@Transactional
public void cleanExpiredTokens() {
// Datenbankarbeit
}
}

Problem:


run() ruft cleanExpiredTokens() in derselben Klasse auf
das kann das transaktionale Proxy-Verhalten umgehen

Besser:


transaktionale Logik in einen anderen Service-Bean auslagern

Merksatz:

Scheduling plus Self-Invocation kann proxy-basierte Annotationen aushebeln.


22. Was ist @Async?

@Async sagt Spring:


Führe diese Methode asynchron aus, üblicherweise in einem anderen Thread.

Beispiel:


@Service
public class EmailService {

@Async
public void sendWelcomeEmail(String email) {
// send email in background
}
}

Aufrufer:


emailService.sendWelcomeEmail("user@example.com");

Der Aufrufer kann weitermachen, ohne auf das Ende der Methode zu warten.

Merksatz:

@Async führt eine Methode in einem anderen Thread aus.


23. Async aktivieren

Um @Async zu nutzen, aktivierst du Async-Unterstützung:


@Configuration
@EnableAsync
public class AsyncConfig {
}

Oder:


@SpringBootApplication
@EnableAsync
public class Application {
}

Merksatz:

@EnableAsync aktiviert die Verarbeitung asynchroner Methoden.


24. Async muss über den Proxy laufen

Wie @Transactional ist @Async proxy-basiert.

Funktioniert:


Controller -> EmailService-Proxy -> Async-Executor -> echte Methode

Funktioniert nicht:


EmailService.this.sendWelcomeEmail()

Beispiel-Falle:


@Service
public class UserService {

public void registerUser() {
sendWelcomeEmail();
}

@Async
public void sendWelcomeEmail() {
// läuft vielleicht nicht async
}
}

Das ist Self-Invocation.

Merksatz:

@Async braucht einen Aufruf über den Spring-Proxy.


25. Async-Rückgabetypen

Gängige Async-Rückgabetypen:


void
CompletableFuture<T>
Future<T>

Beispiel:


@Async
public CompletableFuture<String> generateReport() {
String result = "report";
return CompletableFuture.completedFuture(result);
}

Aufrufer:


CompletableFuture<String> future = reportService.generateReport();

Merksatz:

Nutze CompletableFuture, wenn der Aufrufer ein asynchrones Ergebnis braucht.


26. Async-Void-Exception-Falle

Async-void-Methode:


@Async
public void sendEmail() {
throw new RuntimeException("Email failed");
}

Problem:


der Aufrufer erhält die Exception nicht direkt
die Exception passiert in einem anderen Thread
muss über Async-Exception-Handling geloggt/behandelt werden

Besser für Ergebnis-/Fehlerbehandlung:


@Async
public CompletableFuture<Void> sendEmail() {
try {
// send email
return CompletableFuture.completedFuture(null);
} catch (Exception exception) {
return CompletableFuture.failedFuture(exception);
}
}

Merksatz:

Exceptions in async-Methoden verhalten sich nicht wie normale synchrone Exceptions.


27. Was ist ein Executor?

Ein Executor führt Tasks aus.

Bei Spring Async entscheidet ein Executor:


welcher Thread die async-Methode ausführt
wie viele Threads laufen können
was passiert, wenn die Queue voll ist
Thread-Namen
Rejection Policy

Gängige Klasse:


ThreadPoolTaskExecutor

Merksatz:

Ein Executor verwaltet Threads für async-Arbeit.


28. Async-Executor konfigurieren

Beispiel:


@Configuration
@EnableAsync
public class AsyncConfig {

@Bean(name = "applicationTaskExecutor")
public Executor applicationTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("app-async-");

executor.initialize();

return executor;
}
}

Nutzen:


@Async("applicationTaskExecutor")
public void sendEmail(String email) {
}

Merksatz:

Konfiguriere Executors in der Produktion explizit für async-Arbeit.


29. Thread-Pool-Konzepte

KonzeptBedeutung
Core pool sizenormale Anzahl Threads
Max pool sizemaximal erlaubte Threads
Queue capacitywie viele Tasks warten können
Thread name prefixhilft bei Logs/Debugging
Rejection policywas passiert, wenn voll

Merksatz:

Thread-Pools brauchen Grenzen.


30. Thread-Pool-Falle

Schlechte Denkweise:


Async bedeutet unbegrenzte Hintergrund-Magie.

Realität:


Threads sind begrenzt
CPU ist begrenzt
Speicher ist begrenzt
Datenbankverbindungen sind begrenzt
externe APIs sind begrenzt
Queues können voll werden

Wenn zu viele async-Tasks eingereicht werden:


die Anwendung kann langsamer werden
Speicher kann wachsen
Tasks können abgelehnt werden
Datenbank kann überlastet werden
externe API kann überlastet werden

Merksatz:

Async entfernt keine Arbeit; es verschiebt Arbeit in einen anderen Thread.


31. Async und Transaktionen

Wichtig:


Die async-Methode läuft in einem anderen Thread.
Der Transaktionskontext wird nicht automatisch in diesen Thread übernommen.

Beispiel:


@Transactional
public void registerUser() {
userRepository.save(user);

emailService.sendWelcomeEmailAsync(user.getEmail());
}

Async-Methode:


@Async
public void sendWelcomeEmailAsync(String email) {
}

Problem:


die async-Methode kann laufen, bevor die Transaktion committed ist
bei Rollback kann die E-Mail trotzdem gesendet werden

Besser:


Event veröffentlichen
mit @TransactionalEventListener(AFTER_COMMIT) behandeln
optional @Async hinzufügen

Merksatz:

Async wartet nicht automatisch auf den Transaktions-Commit.


32. Gutes Muster: After Commit + Async

Publisher:


@Transactional
public UserDto register(RegisterUserRequest request) {
UserEntity user = userRepository.save(new UserEntity(request.email()));

eventPublisher.publishEvent(
new UserRegisteredEvent(user.getId(), user.getEmail())
);

return new UserDto(user.getId(), user.getEmail());
}

Listener:


@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void sendWelcomeEmail(UserRegisteredEvent event) {
emailService.sendWelcomeEmail(event.email());
}

Bedeutung:


erst nach erfolgreichem Commit
dann E-Mail in einem anderen Thread ausführen

Merksatz:

Für Nebeneffekte nach DB-Commit kombinierst du transaktionale Events und async sorgfältig.


33. Async und Security Context

Async läuft in einem anderen Thread.

Deshalb ist thread-lokaler Kontext vielleicht nicht automatisch verfügbar.

Beispiele:


SecurityContext
MDC-Logging-Kontext
LocaleContext
Transaktionskontext
Request-Kontext

Schlechte Annahme:


Die async-Methode sieht automatisch denselben Request-User.

Besser:


benötigte Werte explizit übergeben
userId
tenantId
email
correlationId

Merksatz:

In async-Methoden übergibst du benötigten Kontext explizit.


34. Async und Request Scope

Verlasse dich nicht auf request-scoped Objekte in async-Methoden.

Schlecht:


@Async
public void doWork() {
requestScopedBean.getSomething();
}

Problem:


der HTTP-Request ist vielleicht schon beendet
Request-Kontext existiert im async-Thread vielleicht nicht

Besser:


benötigte Daten vor dem async-Aufruf extrahieren
einfache Werte in die async-Methode übergeben

Merksatz:

Async-Arbeit sollte nicht davon abhängen, dass der HTTP-Request noch existiert.


35. Scheduled Jobs bei mehreren App-Instanzen

In der Produktion laufen oft mehrere App-Instanzen.

Beispiel:


Instanz A
Instanz B
Instanz C

Wenn alle denselben Scheduled Job ausführen:


Cleanup-Job läuft dreimal
täglicher Report sendet drei E-Mails
Billing-Job belastet Kunden dreimal

Das ist gefährlich.

Lösungen:


verteilter Lock
Leader Election
externer Scheduler
Datenbank-Lock
ShedLock
Quartz Cluster Mode
Kubernetes CronJob
Job nur auf einer Worker-Instanz ausführen

Merksatz:

In Multi-Instance-Produktion kann jede Instanz den Scheduled Job ausführen.


36. Idempotenz

Scheduled Jobs sollten oft idempotent sein.

Idempotent bedeutet:


mehrfaches Ausführen hat denselben Endeffekt wie einmaliges Ausführen

Guter Cleanup-Job:


abgelaufene Tokens löschen

Meist sicher bei Wiederholung.

Gefährlicher Job:


Kunden belasten
Rechnungs-E-Mail senden
monatliche Rechnung erstellen

Braucht Schutz.

Merksatz:

Scheduled Jobs sollten retry-sicher sein oder vor Duplikaten geschützt werden.


37. Lang laufende Jobs

Lang laufende Jobs brauchen Sorgfalt.

Probleme:


Job überlappt mit dem nächsten Lauf
Transaktion bleibt zu lange offen
Speicher wächst
Datenbank-Locks werden zu lange gehalten
Shutdown unterbricht den Job
kein Fortschritts-Tracking
keine Retry-Strategie

Besser:


in Batches verarbeiten
pro Batch committen
Fortschritt speichern
Locking nutzen
Dauer überwachen
Timeouts setzen
Retry-sicher machen

Merksatz:

Lange Jobs sollten gebatcht, überwacht und retry-sicher sein.


38. Scheduled-Job-Beispiel: Batch-Cleanup

Schlecht:


@Scheduled(cron = "0 0 2 * * *")
@Transactional
public void deleteAllOldData() {
repository.deleteEverythingOlderThan(cutoff);
}

Mögliches Problem:


riesige Transaktion
sperrt viele Zeilen
langsames Rollback bei Fehler
Datenbankdruck

Besser:


@Scheduled(cron = "0 0 2 * * *")
public void cleanupOldData() {
int deleted;

do {
deleted = cleanupService.deleteNextBatch(500);
} while (deleted > 0);
}

Service:


@Transactional
public int deleteNextBatch(int size) {
return repository.deleteNextExpiredBatch(size);
}

Merksatz:

Für große Jobs bevorzugst du kleinere transaktionale Batches.


39. Monitoring von Scheduled Jobs

Scheduled Jobs in der Produktion sollten beobachtbar sein.

Erfasse:


letzter erfolgreicher Lauf
letzter Fehler
Dauer
Anzahl verarbeiteter Elemente
Anzahl übersprungener Elemente
Anzahl Fehler
nächster erwarteter Lauf

Nutze:


Logs
Metriken
Actuator
Alerts
Datenbank-Job-Historie-Tabelle
externes Monitoring

Merksatz:

Wenn ein Scheduled Job wichtig ist, überwache ihn.


40. Actuator Scheduled Tasks

Spring Boot Actuator kann bei entsprechender Konfiguration Informationen zu geplanten Tasks bereitstellen.

Nützlicher Endpoint:


/actuator/scheduledtasks

Er kann helfen, Scheduled Jobs zu inspizieren.

Merksatz:

Actuator kann helfen, geplante Tasks zu beobachten.


41. Geplante Logik testen

Warte in Tests nicht auf echte Zeit.

Schlecht:


Thread.sleep(60_000);

Besser:


Logik in einen Service auslagern
Service direkt unit-testen
Scheduling-Annotation nur bei Bedarf leicht testen
nur wichtiges Wiring integration-testen

Beispiel:


@Component
public class CleanupJob {

private final CleanupService cleanupService;

public CleanupJob(CleanupService cleanupService) {
this.cleanupService = cleanupService;
}

@Scheduled(fixedDelay = 60_000)
public void run() {
cleanupService.cleanup();
}
}

Unit-Test:


class CleanupServiceTest {
// test cleanup business logic directly
}

Merksatz:

Teste geplante Business-Logik wie normale Service-Logik.


42. Async-Logik testen

Verlasse dich nicht auf zufällige Sleeps.

Schlecht:


Thread.sleep(1000);
verify(emailService).sendEmail(...);

Bessere Optionen:


async-Methodenlogik synchron als normale Methode testen
Executor für Unit-Tests mocken
Awaitility für Integrationstests nutzen
CompletableFuture zurückgeben und Ergebnis abwarten

Beispiel:


CompletableFuture<String> future = reportService.generateReport();

assertThat(future.get()).isEqualTo("report");

Merksatz:

Vermeide sleep-basierte async-Tests.


43. Async ist kein Messaging

Wichtig:


@Async ist nicht Kafka.
@Async ist nicht RabbitMQ.
@Async ist nicht dauerhaft.
@Async überlebt keinen App-Crash.
@Async garantiert kein Retry.

Nutze Message Broker/Outbox für:


dauerhafte Zustellung
kommunikation zwischen Services
Retry
Replay
Backpressure
lang laufende verteilte Workflows

Merksatz:

Async ist In-Process-Hintergrundausführung, kein dauerhaftes Messaging.


44. Scheduling ist keine Queue

Wichtig:


@Scheduled ist keine Job-Queue.

Es bietet nicht automatisch:


dauerhafte Job-Speicherung
Retry-Historie
verteiltes Locking
Cluster-Koordination
manuelles Replay
erweiterte Kalender
Job-Dashboard

Für fortgeschrittene Jobs erwäge:


Quartz
Spring Batch
Kubernetes CronJob
Message Queue
Workflow Engine
externer Scheduler

Merksatz:

@Scheduled ist einfaches Scheduling, keine vollständige Job-Plattform.


45. Wann @Scheduled verwenden

Gute Anwendungsfälle:


einfaches Cleanup
einfaches Polling
periodisches Cache-Refresh
täglichen Report auslösen
Health-Check-Task
kleiner interner Wartungsjob

Vorsicht bei:


Abrechnung
Zahlungen
kritische E-Mails
große Batch-Jobs
verteilte Jobs
Jobs mit Exactly-Once-Ausführung
Jobs mit Retry und Audit Trail

Merksatz:

Nutze @Scheduled für einfache periodische Jobs.


46. Wann @Async verwenden

Gute Anwendungsfälle:


unkritische Hintergrundarbeit
parallele unabhängige Arbeit
E-Mail-Versand nach Commit
Report-Generierung
Benachrichtigungsversand
externer API-Aufruf, der den Aufrufer nicht blockieren soll

Vorsicht bei:


Arbeit, die dauerhaft sein muss
Arbeit, die retried werden muss
Arbeit, die Request-/Transaktionskontext braucht
unbegrenzte Workloads
sicherheitsrelevanter Kontext

Merksatz:

Nutze @Async für lokale Hintergrundarbeit, nicht für garantierte Zustellung.


47. Häufige Prüfungsfallen

Falle 1

@Scheduled braucht aktivierte Scheduling-Unterstützung.


Falle 2

@Async braucht aktivierte Async-Unterstützung.


Falle 3

Geplante Methoden sollten keine Argumente erwarten.


Falle 4

Rückgabewerte geplanter Methoden werden ignoriert.


Falle 5

fixedRate misst Start-zu-Start.


Falle 6

fixedDelay misst Ende-zu-nächster-Start.


Falle 7

Cron ist für kalenderbasierte Zeitpläne gedacht.


Falle 8

Die Cron-Zeitzone ist wichtig.


Falle 9

@Async läuft in einem anderen Thread.


Falle 10

Async-Exceptions verhalten sich nicht wie normale synchrone Exceptions.


Falle 11

@Async ist proxy-basiert.


Falle 12

Self-Invocation kann @Async umgehen.


Falle 13

Async teilt den Transaktionskontext nicht automatisch.


Falle 14

Async wartet nicht automatisch auf den Commit.


Falle 15

Jede App-Instanz kann denselben Scheduled Job ausführen.


Falle 16

Scheduled Jobs sollten idempotent sein oder gelockt werden.


Falle 17

@Scheduled ist kein verteiltes Job-System.


Falle 18

@Async ist kein dauerhaftes Messaging.


48. Echte Prüfungsfrage: @Scheduled

Frage:

Was macht @Scheduled?

Antwort:

@Scheduled markiert eine Spring-Bean-Methode zur automatischen Ausführung nach einem Zeitplan, z. B. fixed rate, fixed delay oder Cron.


49. Echte Prüfungsfrage: Scheduling aktivieren

Frage:

Welche Annotation aktiviert die Verarbeitung geplanter Methoden?

Antwort:

@EnableScheduling.


50. Echte Prüfungsfrage: Fixed Rate

Frage:

Was bedeutet fixed rate?

Antwort:

Fixed rate führt Ausführungen in einem regelmäßigen Intervall aus, gemessen ab der Startzeit der vorherigen Ausführung.


51. Echte Prüfungsfrage: Fixed Delay

Frage:

Was bedeutet fixed delay?

Antwort:

Fixed delay wartet ein konfiguriertes Intervall, nachdem die vorherige Ausführung beendet ist, bevor die nächste startet.


52. Echte Prüfungsfrage: Cron

Frage:

Wann solltest du Cron verwenden?

Antwort:

Nutze Cron für kalenderbasierte Zeitpläne, z. B. jeden Tag um 02:00 Uhr oder jeden Montag um 08:00 Uhr.


53. Echte Prüfungsfrage: @Async

Frage:

Was macht @Async?

Antwort:

@Async lässt eine Spring-Bean-Methode asynchron ausführen, üblicherweise in einem anderen Thread über einen Executor.


54. Echte Prüfungsfrage: Async aktivieren

Frage:

Welche Annotation aktiviert die Verarbeitung asynchroner Methoden?

Antwort:

@EnableAsync.


55. Echte Prüfungsfrage: Executor

Frage:

Was ist ein Executor?

Antwort:

Ein Executor verwaltet Threads und führt eingereichte Tasks aus, einschließlich Methoden mit @Async.


56. Echte Prüfungsfrage: Async und Transaktion

Frage:

Setzt @Async die Transaktion des Aufrufers automatisch fort?

Antwort:

Nein. Async-Methoden laufen in einem anderen Thread und teilen den Transaktionskontext des Aufrufers nicht automatisch.


57. Interview-Antwort

Frage:

Erkläre fixed rate vs. fixed delay.

Gute Antwort:

Fixed rate misst das Intervall von der Startzeit einer Ausführung zur Startzeit der nächsten. Fixed delay wartet, bis die vorherige Ausführung beendet ist, und wartet dann die konfigurierte Verzögerung, bevor es erneut startet. Fixed delay ist oft sicherer für Jobs, bei denen du Druck durch langsame Ausführungen vermeiden willst.


58. Interview-Antwort

Frage:

Wie nutzt du @Async sicher?

Gute Antwort:

Du aktivierst Async-Unterstützung mit @EnableAsync, konfigurierst einen begrenzten Executor mit klaren Thread-Pool-Einstellungen und stellst sicher, dass die async-Methode über einen Spring-Proxy aufgerufen wird. Du verlässt dich nicht darauf, dass Transaktions-, Request- oder Security-Kontext automatisch verfügbar sind. Für Nebeneffekte nach Datenbank-Commit veröffentlichst du lieber ein Event und behandelst es mit @TransactionalEventListener(AFTER_COMMIT) plus @Async.


59. Interview-Antwort

Frage:

Welche Produktionsrisiken gibt es bei @Scheduled?

Gute Antwort:

In der Produktion können Scheduled Jobs versehentlich auf jeder Anwendungsinstanz laufen und doppelte Arbeit verursachen. Lang laufende Jobs können überlappen, Locks halten oder die Datenbank überlasten. Jobs brauchen Exception-Handling, Monitoring, Idempotenz und oft verteiltes Locking oder einen externen Scheduler. @Scheduled ist gut für einfache Jobs, aber keine vollständige verteilte Job-Plattform.


60. Interview-Antwort

Frage:

Ist @Async dasselbe wie Messaging?

Gute Antwort:

Nein. @Async führt Arbeit nur in einem anderen Thread innerhalb desselben Anwendungsprozesses aus. Es ist nicht dauerhaft, überlebt keine Anwendungsabstürze und bietet kein Broker-Level-Retry oder Replay. Für dauerhafte kommunikation zwischen Services würdest du einen Message Broker wie Kafka oder RabbitMQ oder ein Outbox-Pattern nutzen.


61. Kleine Code-Übung

Erstelle einen Cleanup-Job, der alle 10 Minuten läuft, nachdem der vorherige Lauf beendet ist.

Mögliche Antwort:


@Component
public class CleanupJob {

private final CleanupService cleanupService;

public CleanupJob(CleanupService cleanupService) {
this.cleanupService = cleanupService;
}

@Scheduled(fixedDelay = 10, timeUnit = TimeUnit.MINUTES)
public void clean() {
cleanupService.cleanExpiredData();
}
}

Frage:

Warum fixed delay?

Antwort:


Weil die nächste Ausführung erst startet, nachdem die vorherige beendet ist und die Verzögerung abgelaufen ist.

62. Kleine Code-Übung 2

Erstelle einen asynchronen E-Mail-Sender.


@Configuration
@EnableAsync
public class AsyncConfig {

@Bean(name = "emailExecutor")
public Executor emailExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("email-");
executor.initialize();
return executor;
}
}

@Service
public class EmailService {

@Async("emailExecutor")
public void sendWelcomeEmail(String email) {
// send email
}
}

Merksatz:

Benannte Executors machen async-Verhalten explizit.


63. Kleine Bug-Übung 1

Problem:


@Scheduled(fixedDelay = 5000)
public void run(String input) {
}

Frage:

Was ist falsch?

Antwort:

Geplante Methoden sollten keine Argumente erwarten, weil Spring sie automatisch aufruft.


64. Kleine Bug-Übung 2

Problem:


@Service
public class UserService {

public void register() {
sendEmail();
}

@Async
public void sendEmail() {
}
}

Frage:

Warum läuft sendEmail() vielleicht nicht asynchron?

Antwort:

Das ist Self-Invocation. Der Methodenaufruf läuft nicht über den Spring-Proxy.


65. Kleine Bug-Übung 3

Problem:


@Transactional
public void registerUser() {
userRepository.save(user);
emailService.sendWelcomeEmailAsync(user.getEmail());
throw new RuntimeException("rollback");
}

Frage:

Was ist gefährlich?

Antwort:

Die async-E-Mail kann gesendet werden, obwohl die Transaktion zurückgerollt wird. Nutze für diesen Nebeneffekt ein AFTER_COMMIT-transaktionales Event.


66. Kleine Bug-Übung 4

Problem:


App hat 3 Produktionsinstanzen.
Jede Instanz hat einen @Scheduled täglichen Billing-Job.

Frage:

Was kann schiefgehen?

Antwort:

Der Billing-Job kann dreimal laufen und Kunden möglicherweise mehrfach belasten. Nutze verteiltes Locking, Leader Election, einen externen Scheduler oder mache den Job idempotent.


Übungsfragen

Frage 1

Was ist Scheduling?

Antwort:

Scheduling bedeutet, eine Aufgabe automatisch zu einer geplanten Zeit oder in einem Intervall auszuführen.


Frage 2

Was macht @Scheduled?

Antwort:

@Scheduled markiert eine Spring-Bean-Methode zur automatischen Ausführung nach fixed rate, fixed delay oder Cron.


Frage 3

Welche Annotation aktiviert Scheduling?

Antwort:

@EnableScheduling.


Frage 4

Was ist fixed rate?

Antwort:

Fixed rate bedeutet, dass das Intervall von der Startzeit einer Ausführung zur Startzeit der nächsten gemessen wird.


Frage 5

Was ist fixed delay?

Antwort:

Fixed delay bedeutet, dass Spring wartet, bis die vorherige Ausführung beendet ist, und dann die konfigurierte Verzögerung abwartet, bevor es erneut startet.


Frage 6

Wofür wird Cron verwendet?

Antwort:

Cron wird für kalenderbasierte Zeitpläne verwendet, z. B. jeden Tag um 02:00 Uhr.


Frage 7

Warum ist die Cron-Zeitzone wichtig?

Antwort:

Weil Server vielleicht in UTC laufen, der Geschäftszeitplan aber eine bestimmte Zeitzone wie Europe/Berlin braucht, und die Sommerzeit relevant sein kann.


Frage 8

Welche Regeln gelten für geplante Methoden?

Antwort:

Geplante Methoden sollten üblicherweise keine Argumente haben, void zurückgeben oder der Rückgabewert wird ignoriert, auf von Spring verwalteten Beans liegen und Exceptions sorgfältig behandeln.


Frage 9

Was macht @Async?

Antwort:

@Async lässt eine Spring-Bean-Methode asynchron laufen, üblicherweise in einem anderen Thread.


Frage 10

Welche Annotation aktiviert Async?

Antwort:

@EnableAsync.


Frage 11

Was ist ein Executor?

Antwort:

Ein Executor verwaltet Threads und führt eingereichte Tasks aus.


Frage 12

Warum sollten Thread-Pools begrenzt sein?

Antwort:

Weil Threads, Speicher, Datenbankverbindungen und externe Services begrenzt sind. Unbegrenzte async-Arbeit kann die Anwendung überlasten.


Frage 13

Setzt Async die Transaktion des Aufrufers automatisch fort?

Antwort:

Nein. Async-Methoden laufen in einem anderen Thread und teilen den Transaktionskontext des Aufrufers nicht automatisch.


Frage 14

Warum kann Self-Invocation @Async aushebeln?

Antwort:

Weil Self-Invocation die Methode auf this aufruft und damit den Spring-Proxy umgeht, der das async-Verhalten anwendet.


Frage 15

Warum sollten async-Methoden sich nicht auf Request-Kontext verlassen?

Antwort:

Weil der HTTP-Request vielleicht schon beendet ist und Request-Kontext im async-Thread vielleicht nicht existiert.


Frage 16

Welches Problem entsteht bei Scheduled Jobs mit mehreren App-Instanzen?

Antwort:

Jede Instanz kann denselben Job ausführen und doppelte Arbeit verursachen.


Frage 17

Was bedeutet idempotent?

Antwort:

Idempotent bedeutet, dass dieselbe Operation mehrfach ausgeführt denselben Endeffekt hat wie einmalige Ausführung.


Frage 18

Warum ist @Async kein dauerhaftes Messaging?

Antwort:

Weil @Async Arbeit innerhalb desselben Anwendungsprozesses ausführt und keine dauerhafte Speicherung, Retry, Replay oder Crash-Recovery bietet.


Frage 19

Warum ist @Scheduled keine vollständige Job-Plattform?

Antwort:

Weil @Scheduled von sich aus keine dauerhafte Job-Speicherung, verteiltes Locking, Retry-Historie, Cluster-Koordination oder erweitertes Job-Management bietet.


Frage 20

Wie solltest du geplante und async-Logik testen?

Antwort:

Lagere Business-Logik in Services aus und teste diese Services direkt. Vermeide sleep-basierte Tests. Für async gib CompletableFuture zurück oder nutze passende Warte-Tools in Integrationstests.

Merksätze zum Mitnehmen

  • Scheduling dreht sich um Zeit.
  • Async dreht sich um Threads.
  • @Scheduled führt Methoden automatisch aus.
  • @EnableScheduling aktiviert Scheduling.
  • fixedRate bedeutet Start-zu-Start.
  • fixedDelay bedeutet Ende-zu-nächster-Start.
  • Cron ist für Kalender-Zeitpläne gedacht.
  • Die Cron-Zeitzone ist wichtig.
  • Geplante Methoden sollten keine Argumente erwarten.
  • Rückgabewerte geplanter Methoden werden ignoriert.
  • Scheduled Jobs sollten Exceptions behandeln.
  • Scheduled Jobs sollten Service-Logik aufrufen.
  • @Async führt Methoden in einem anderen Thread aus.
  • @EnableAsync aktiviert Async.
  • @Async ist proxy-basiert.
  • Self-Invocation kann @Async umgehen.
  • Async teilt den Transaktionskontext nicht automatisch.
  • Async wartet nicht automatisch auf den Commit.
  • Übergib benötigten Kontext explizit in async-Methoden.
  • Jede Produktionsinstanz kann denselben Scheduled Job ausführen.
  • Scheduled Jobs sollten idempotent sein oder gelockt werden.
  • @Scheduled ist keine verteilte Job-Plattform.
  • @Async ist kein dauerhaftes Messaging.