0Pricing
Spring Boot 4 Complete Guide · 강의

내결함성, 건너뛰기 및 재시도 정책

일시적인 데이터 오류에도 작업이 견고하게 동작하도록 건너뛰기, 재시도 및 재시작 의미론을 구성합니다.

내결함성, 건너뛰기 및 재시도 정책은(는) CoddyKit의 무료 Spring Boot 4 Complete Guide 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 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 AI 튜터), CoddyKit PRO로 업그레이드하면 Spring Boot 4 Complete Guide 강의 전체를 잠금 해제할 수 있습니다. Spring Boot 4 Complete Guide 강의에는 총 4개의 강의가 포함되어 있습니다.

“내결함성, 건너뛰기 및 재시도 정책”에서 뭘 배우나요?

일시적인 데이터 오류에도 작업이 견고하게 동작하도록 건너뛰기, 재시도 및 재시작 의미론을 구성합니다. 브라우저에서 직접 실행하는 실습 코드로 Spring Boot 4 Complete Guide을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Spring Boot 4 Complete Guide을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Spring Boot 4 Complete Guide은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.

“내결함성, 건너뛰기 및 재시도 정책” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Spring Boot 4 Complete Guide 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Spring Boot 4 Complete Guide 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 작업, 단계 및 JobRepository 모델
  2. 청크 중심 Reader-Processor-Writer 흐름
  3. 내결함성, 건너뛰기 및 재시도 정책
  4. 파티셔닝과 병렬 단계 실행
← Spring Boot 4 Complete Guide(으)로 돌아가기