TS
Thomas Schmitz

Freiberuflicher IT-Berater, Softwareentwickler & DevOps. Praxisnahe Artikel zu API-Design, Architektur und Cloud-Workflows.

Serie: Spring Batch · Teil 7 von 12

Spring Batch Teil 7: Fehlerbehandlung & Validierung

Skip, Retry, Validierung: wie du Fehler klassifizierst und den Batch robust machst.

Praxisnah Checkliste

Fehler sind in Batches normal. Die Frage ist nicht ob sie auftreten, sondern wie du damit umgehst. Ohne klare Fehlerstrategie werden Runs instabil: mal wird abgebrochen, mal wird unbemerkt Mist geschrieben. Für Einsteiger heißt das: Du brauchst Regeln, welche Fehler den Job stoppen und welche du tolerieren darfst.

In diesem Teil definieren wir eine Fehlerklassifikation und setzen Skip/Retry sauber auf. Ziel ist ein Batch, der verlässlich ist: er bricht bei echten Problemen ab, toleriert aber erwartbare Datenfehler.

Zielbild

Nach diesem Kapitel kannst du Fehler nach Schwere und Ursache klassifizieren, Skip und Retry zielgerichtet konfigurieren und Validierung so platzieren, dass Fehler früh sichtbar werden.

Fehlerklassifikation

Wir arbeiten mit drei Klassen:

  1. Fatal: der Job muss abbrechen (z. B. DB nicht erreichbar).
  2. Retryable: temporär, lohnt Wiederholung (z. B. Deadlock).
  3. Skippable: einzelne Datensätze sind fehlerhaft, Job kann weiterlaufen.

Diese Klassifikation bestimmt die Policies, nicht der Bauch.

Validierung: so früh wie möglich

Validierung gehört an den Eingang, entweder im FieldSetMapper für Format und Pflichtfelder oder im ItemProcessor für fachliche Regeln. Beispiel: Pflichtfeld email fehlt → Validierungsfehler.

Fehler sollen früh sichtbar werden, bevor schon etwas geschrieben wurde.

Skip-Strategie

Skips sind sinnvoll bei Datenfehlern, nicht bei Infrastrukturproblemen.

Vollständiges Minimalbeispiel. Als Domänentypen nutze ich hier String als Platzhalter. Idee: CSV-Zeilen mit Parserfehlern werden bis zu einem Limit übersprungen, der Rest läuft weiter.

import java.util.Set;

import org.springframework.batch.core.Step;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.skip.LimitCheckingExceptionHierarchySkipPolicy;
import org.springframework.batch.core.step.skip.SkipPolicy;
import org.springframework.batch.infrastructure.item.ItemProcessor;
import org.springframework.batch.infrastructure.item.ItemReader;
import org.springframework.batch.infrastructure.item.ItemWriter;
import org.springframework.batch.infrastructure.item.file.FlatFileParseException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
public class ImportStepConfig {

    @Bean
    public Step importStep(JobRepository jobRepository,
                           PlatformTransactionManager transactionManager,
                           ItemReader<String> reader,
                           ItemProcessor<String, String> processor,
                           ItemWriter<String> writer) {

        int skipLimit = 100;
        var skippableExceptions = Set.of(FlatFileParseException.class);
        SkipPolicy skipPolicy =
                new LimitCheckingExceptionHierarchySkipPolicy(skippableExceptions, skipLimit);

        return new StepBuilder("importStep", jobRepository)
                .<String, String>chunk(200)
                .transactionManager(transactionManager)
                .reader(reader)
                .processor(processor)
                .writer(writer)
                .faultTolerant()
                .skipPolicy(skipPolicy)
                .build();
    }
}

Wichtig: Skip-Limits gelten global pro Step, nicht pro Chunk. Zu hohe Limits verstecken Datenprobleme. Skips sind ein operativer KPI (Kennzahl) und gehören ins Monitoring. Beispiel: Limit 100 → der 101. fehlerhafte Datensatz stoppt den Step.

Retry-Strategie

Retries sind für transiente Fehler gedacht, typischerweise DB-Locks und Deadlocks, Netzwerk-Hänger oder kurzfristige Resource-Limits.

Vollständiges Minimalbeispiel. Als Domänentypen nutze ich hier String als Platzhalter.

import org.springframework.batch.core.Step;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.infrastructure.item.ItemProcessor;
import org.springframework.batch.infrastructure.item.ItemReader;
import org.springframework.batch.infrastructure.item.ItemWriter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DeadlockLoserDataAccessException;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
public class RetryStepConfig {

    @Bean
    public Step retryingImportStep(JobRepository jobRepository,
                                   PlatformTransactionManager transactionManager,
                                   ItemReader<String> reader,
                                   ItemProcessor<String, String> processor,
                                   ItemWriter<String> writer) {

        return new StepBuilder("importStep", jobRepository)
                .<String, String>chunk(200)
                .transactionManager(transactionManager)
                .reader(reader)
                .processor(processor)
                .writer(writer)
                .faultTolerant()
                .retry(DeadlockLoserDataAccessException.class)
                .retryLimit(3)
                .build();
    }
}

Wichtig: Retries erhöhen Laufzeit und können Side-Effects doppeln. Processor und Writer müssen idempotent sein. Idempotent heißt: Derselbe Datensatz kann mehrmals verarbeitet werden, ohne dass doppelte Ergebnisse entstehen.

Validierungsfehler als Skips behandeln

Ein typisches Muster: Parsing-Fehler werden mit Limit geskippt, fachliche Validierung führt je nach Schwere zu Skip oder Fail.

Beispiel: fehlende E-Mail → Skip, ungültige Kundennummer → Fail.

Anti-Patterns

Retry auf Datenfehler verschwendet Zeit und verschleiert die Ursache. Skip-Limit sehr hoch führt zu einem erfolgreichen Job mit katastrophalen Daten. Validierung im Writer macht Fehler spät sichtbar und vergrößert Rollbacks. Alle Exceptions als retryable produziert lange, instabile Runs.

Kurzfazit

Fehlerklassen sind eine Betriebsentscheidung. Skip ist für Datenfehler, Retry für transiente Infrastrukturprobleme. Validierung muss früh und explizit passieren.

Im nächsten Teil gehen wir auf Datenqualität und Konsistenz ein: Duplikate, Referenzen, Late Data und wie du sie im Batch erkennst.

Alle Teile der Serie: Serie: Spring Batch

Mehr Beiträge aus dem Blog.