0Pricing
Spring Boot 4 Complete Guide · Урок

Отказоустойчивость, пропуск и политики повторных попыток

Настраивайте семантику пропуска, повторной попытки и перезапуска, чтобы задания выдерживали временные ошибки данных.

«Отказоустойчивость, пропуск и политики повторных попыток» — бесплатный урок Spring Boot 4 Complete Guide на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Spring Boot 4 Complete Guide, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Spring Boot 4 Complete Guide содержит 4 уроков всего.

Части этого урока еще не переведены и отображаются на английском.

Why Fault Tolerance Matters

Batch jobs process huge volumes of data, and real-world data is messy. A single malformed row, a momentary database deadlock, or a flaky downstream call can blow up a job that has already processed millions of records.

Spring Batch gives a chunk-oriented step fault tolerance so it can survive these issues without aborting the whole run. The three core tools are:

  • Skip — discard records that cause unrecoverable errors (e.g. bad data) and keep going.
  • Retry — re-attempt an operation that failed due to a transient error (e.g. a lock timeout).
  • Restart — resume a failed job instance from where it stopped instead of starting over.

Used together, these turn a brittle job into a resilient one.

Enabling Fault Tolerance on a Step

Fault tolerance is opt-in. When you build a chunk step, call .faultTolerant() on the step builder to switch to the fault-tolerant variant. Only then can you declare skip and retry rules.

Without .faultTolerant(), any exception thrown by a reader, processor, or writer rolls back the chunk and fails the step immediately.

@Bean
public Step importStep(JobRepository jobRepository,
                        PlatformTransactionManager txManager,
                        ItemReader<Customer> reader,
                        ItemProcessor<Customer, Customer> processor,
                        ItemWriter<Customer> writer) {
    return new StepBuilder("importStep", jobRepository)
            .<Customer, Customer>chunk(100, txManager)
            .reader(reader)
            .processor(processor)
            .writer(writer)
            .faultTolerant()   // unlocks skip & retry configuration
            .build();
}

Configuring Skip Policies

Skipping lets the step throw away an individual item that can never succeed — typically a parsing or validation failure — and continue with the next one.

You declare which exceptions are skippable and a global limit:

  • .skip(Exception.class) — mark an exception type as skippable.
  • .noSkip(Exception.class) — explicitly exclude a subtype from skipping.
  • .skipLimit(n) — total number of skips allowed before the step fails.

Once the cumulative skip count exceeds skipLimit, the step aborts. This prevents a job from silently swallowing thousands of bad records.

return new StepBuilder("importStep", jobRepository)
        .<Customer, Customer>chunk(100, txManager)
        .reader(reader)
        .processor(processor)
        .writer(writer)
        .faultTolerant()
        .skip(FlatFileParseException.class)   // bad CSV line
        .skip(ValidationException.class)      // failed bean validation
        .noSkip(FileNotFoundException.class)  // never skip this
        .skipLimit(50)                        // fail after 50 skips
        .build();

How Skip Interacts with Chunks

Skip behaviour depends on where the exception is thrown:

  • Reader skip: the bad item is dropped and reading continues — cheap, no rollback.
  • Processor skip: the chunk transaction rolls back, then Spring Batch re-processes the chunk item-by-item, skipping only the offending item.
  • Writer skip: same scan-and-retry — the chunk rolls back and items are re-written one at a time so the single bad item can be isolated and skipped.

Because processor/writer skips trigger a rollback and a single-item replay, they are far more expensive than reader skips. Keep validation in the reader/processor where possible so failures are caught early.

A Custom SkipPolicy

The declarative .skip()/.skipLimit() API covers most cases, but you can implement SkipPolicy for fully custom logic — for example, allow more skips for one exception type than another, or inspect the exception message.

The shouldSkip method receives the thrown Throwable and the current skip count; return true to skip, or throw SkipLimitExceededException to fail the step.

public class CustomSkipPolicy implements SkipPolicy {

    @Override
    public boolean shouldSkip(Throwable t, long skipCount)
            throws SkipLimitExceededException {
        if (t instanceof FileNotFoundException) {
            return false; // fatal: never skip
        }
        if (t instanceof ValidationException && skipCount < 100) {
            return true;  // tolerate up to 100 bad records
        }
        if (t instanceof FlatFileParseException && skipCount < 20) {
            return true;
        }
        return false;
    }
}

When to Retry vs Skip

The decision between skip and retry comes down to the nature of the error:

  • Retry a transient error that may succeed if attempted again: deadlock victim, lock timeout, optimistic locking conflict, brief network blip.
  • Skip a deterministic error that will always fail: malformed input, a failed business validation, a constraint that the data itself violates.

Retrying a deterministic error just wastes attempts before failing; skipping a transient error throws away data that would have succeeded. Classify your exceptions correctly — this is the key design decision of the lesson.

Configuring Retry Policies

Retry re-attempts the failing operation up to a configured number of times before giving up. On a fault-tolerant step you declare:

  • .retry(Exception.class) — exception types that are retryable.
  • .noRetry(Exception.class) — exclude a subtype.
  • .retryLimit(n) — maximum attempts per item (including the first try).

When an item fails with a retryable exception, the chunk transaction rolls back and the item is replayed up to retryLimit times. If it still fails, the exception propagates — at which point it may be skipped if it is also declared skippable.

return new StepBuilder("importStep", jobRepository)
        .<Customer, Customer>chunk(100, txManager)
        .reader(reader)
        .processor(processor)
        .writer(writer)
        .faultTolerant()
        .retry(DeadlockLoserDataAccessException.class)
        .retry(OptimisticLockingFailureException.class)
        .retryLimit(3)            // up to 3 attempts per item
        .skip(ValidationException.class)
        .skipLimit(50)
        .build();

Backoff Between Retries

Hammering a contended resource with immediate retries often makes contention worse. A backoff policy inserts a delay between attempts. ExponentialBackOffPolicy grows the wait multiplicatively, spreading out load.

You attach a custom RetryPolicy or BackOffPolicy via .retryPolicy(...) / by configuring a RetryTemplate. Below, the wait starts at 200ms and doubles each attempt up to 5s.

@Bean
public RetryTemplate retryTemplate() {
    ExponentialBackOffPolicy backOff = new ExponentialBackOffPolicy();
    backOff.setInitialInterval(200);   // 200 ms
    backOff.setMultiplier(2.0);        // 200, 400, 800, ...
    backOff.setMaxInterval(5000);      // cap at 5 s

    SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(3,
            Map.of(DeadlockLoserDataAccessException.class, true));

    RetryTemplate template = new RetryTemplate();
    template.setBackOffPolicy(backOff);
    template.setRetryPolicy(retryPolicy);
    return template;
}

Listeners: Observing Skips and Retries

Silently skipping records is dangerous — you need an audit trail. SkipListener callbacks fire for each skipped item so you can log it, write it to a dead-letter table, or alert.

  • onSkipInRead — a read failure was skipped.
  • onSkipInProcess — gives you the item and the exception.
  • onSkipInWrite — the item that could not be written.

Register it with .listener(skipListener) on the step builder. There is also RetryListener for observing retry attempts.

public class LoggingSkipListener implements SkipListener<Customer, Customer> {

    private static final Logger log =
            LoggerFactory.getLogger(LoggingSkipListener.class);

    @Override
    public void onSkipInRead(Throwable t) {
        log.warn("Skipped unreadable record: {}", t.getMessage());
    }

    @Override
    public void onSkipInProcess(Customer item, Throwable t) {
        log.warn("Skipped {} in process: {}", item.getId(), t.getMessage());
    }

    @Override
    public void onSkipInWrite(Customer item, Throwable t) {
        log.warn("Skipped {} in write: {}", item.getId(), t.getMessage());
    }
}

Restartability and the Job Repository

Skip and retry handle errors within a run; restart handles a run that failed completely. Because Spring Batch persists each step's ExecutionContext and read/write counts in the job repository, relaunching the same JobInstance resumes from the last committed chunk rather than reprocessing everything.

Key rules:

  • A JobInstance is identified by its identifying job parameters; reuse them to restart, change them to start a fresh instance.
  • Only jobs in a non-COMPLETED state (e.g. FAILED, STOPPED) can be restarted.
  • Mark a step .allowStartIfComplete(true) to force already-completed steps to re-run on restart.
  • Cap retries with .startLimit(n) so a broken step is not relaunched forever.

Putting It All Together

A production-grade resilient step combines all three concerns: retry transient failures with backoff, skip deterministic bad data within a bounded limit, audit every skip, and rely on the job repository for restart.

Note the layering: an item that fails is first retried; if it still fails and the exception is skippable, it is skipped (and the listener records it). Exceptions can be both retryable and skippable — retry exhausts first, then skip applies.

return new StepBuilder("resilientImport", jobRepository)
        .<Customer, Customer>chunk(100, txManager)
        .reader(reader)
        .processor(processor)
        .writer(writer)
        .faultTolerant()
        // transient -> retry with backoff
        .retry(DeadlockLoserDataAccessException.class)
        .retryLimit(3)
        // deterministic bad data -> skip
        .skip(FlatFileParseException.class)
        .skip(ValidationException.class)
        .skipLimit(100)
        // audit + restart safety
        .listener(new LoggingSkipListener())
        .startLimit(3)
        .build();

Quick Check

Test your understanding of skip vs. retry semantics.

Recap

You made a Spring Batch step resilient to transient and deterministic failures:

  • Enable tolerance with .faultTolerant() before declaring any skip/retry rules.
  • Skip deterministic bad data with .skip() + .skipLimit(); processor/writer skips cost a rollback and single-item replay, so validate early.
  • Retry transient errors with .retry() + .retryLimit(), adding an exponential backoff to ease contention.
  • Classify carefully: retry transient (deadlock, lock timeout), skip deterministic (parse/validation). When an exception is both, retry runs first, then skip.
  • Audit every skip with a SkipListener so nothing disappears silently.
  • Restart failed instances from the last committed chunk via the job repository; control re-runs with allowStartIfComplete and startLimit.

Часто задаваемые вопросы

Урок «Отказоустойчивость, пропуск и политики повторных попыток» бесплатный?

Да — полный текст урока «Отказоустойчивость, пропуск и политики повторных попыток» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Spring Boot 4 Complete Guide, подпишись на CoddyKit PRO. Курс Spring Boot 4 Complete Guide содержит 4 уроков всего.

Чему я научусь в уроке «Отказоустойчивость, пропуск и политики повторных попыток»?

Настраивайте семантику пропуска, повторной попытки и перезапуска, чтобы задания выдерживали временные ошибки данных. Ты практикуешь Spring Boot 4 Complete Guide с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Spring Boot 4 Complete Guide?

Предыдущий опыт не требуется. Spring Boot 4 Complete Guide на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Отказоустойчивость, пропуск и политики повторных попыток»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Spring Boot 4 Complete Guide?

Да. Каждый урок Spring Boot 4 Complete Guide включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Задания, шаги и модель JobRepository
  2. Потоки обработки блоками: чтение, обработка и запись
  3. Отказоустойчивость, пропуск и политики повторных попыток
  4. Разбиение и параллельное выполнение шагов
← Назад к Spring Boot 4 Complete Guide