When would you implement a custom SkipPolicy instead of using skip()/skipLimit(), and how does it work?
answer
- SkipPolicy.shouldSkip(Throwable, long skipCount)
- declarative skip()/skipLimit() == LimitCheckingItemSkipPolicy
- custom = per-exception limits / message-based / percentage
- Always/Never/CompositeSkipPolicy built-ins
- return false OR throw SkipLimitExceededException to fail
basics
~20 sUse a custom SkipPolicy when the skip decision needs logic beyond "is this exception type in the whitelist and under the count limit" — e.g. different limits per exception, inspecting the exception message, or unlimited skips for one type. You implement shouldSkip(Throwable, long) and register it with .skipPolicy(...).
solid answer
~40 sThe declarative .skip(Ex.class).skipLimit(n) combo is sugar that builds a LimitCheckingItemSkipPolicy under the hood. When your skip decision needs richer logic you provide a custom SkipPolicy: a single method boolean shouldSkip(Throwable t, long skipCount) that returns true to skip, false to not skip, and may throw SkipLimitExceededException to fail the step. Register it via .faultTolerant().skipPolicy(myPolicy). Use it for per-exception limits (skip 100 parse errors but only 5 constraint violations), decisions based on the exception's cause or message, time/percentage-based thresholds, or an AlwaysSkipItemSkipPolicy / NeverSkipItemSkipPolicy for extremes. Note that .skipPolicy(...) generally replaces the .skip()/.skipLimit() declarations rather than adding to them, so put all logic in the policy.
code
java · 30 linespublic class TieredSkipPolicy implements SkipPolicy {
private static final int PARSE_LIMIT = 500;
private static final int VALIDATION_LIMIT = 5;
@Override
public boolean shouldSkip(Throwable t, long skipCount)
throws SkipLimitExceededException {
if (t instanceof FlatFileParseException) {
return skipCount < PARSE_LIMIT; // generous for dirty input
}
if (t instanceof ValidationException) {
if (skipCount >= VALIDATION_LIMIT) {
throw new SkipLimitExceededException(VALIDATION_LIMIT, t);
}
return true;
}
return false; // unknown exception -> do not skip, fail the step
}
}
@Bean
public Step step(JobRepository repo, PlatformTransactionManager tx,
ItemReader<Rec> r, ItemWriter<Rec> w) {
return new StepBuilder("step", repo)
.<Rec, Rec>chunk(100, tx)
.reader(r).writer(w)
.faultTolerant()
.skipPolicy(new TieredSkipPolicy())
.build();
}go deeper
Awareness that a SkipPolicy interface exists and shouldSkip decides.
Explain that declarative skip()/skipLimit() = LimitCheckingItemSkipPolicy and when a custom one helps.
Implement per-exception tiered limits and handle the false-vs-throw distinction and replay counting.
Standardize skip policies across jobs (composite, percentage-based, dead-letter integration) and reason about statefulness/testability trade-offs.
**The interface.** `org.springframework.batch.core.step.skip.SkipPolicy` has one method: ```java boolean shouldSkip(Throwable t, long skipCount) throws SkipLimitExceededException; ``` The framework calls it every time a candidate exception occurs during read/process/write. Return `true` to skip the item, `false` to *not* skip (which lets the exception propagate and fail the step), or throw `SkipLimitExceededException` to fail explicitly. `skipCount` is the number of items skipped *so far in this step*. **What the declarative API really is.** `.skip(X.class).skip(Y.class).skipLimit(n)` is not magic — it constructs a `LimitCheckingItemSkipPolicy` configured with the skippable exception classes and the limit. That built-in returns `true` while the exception is a whitelisted (and not `noSkip`-excluded) type and `skipCount < limit`, and throws `SkipLimitExceededException` once the limit is passed. So a custom policy is just you writing that decision yourself. **When to go custom.** - **Per-exception-type limits.** Allow many `FlatFileParseException`s but only a couple of `ConstraintViolationException`s. The single global `skipLimit` can't express this. - **Content-sensitive decisions.** Inspect `t.getCause()`, the message, an error code, or the item's context (via a stateful policy) to decide. - **Percentage/ratio thresholds.** Skip up to, say, 5% of processed items rather than a fixed count (requires you to track totals, often via a listener or shared state). - **Extremes.** `AlwaysSkipItemSkipPolicy` (skip everything — dangerous, use for prototyping) or `NeverSkipItemSkipPolicy` (skip nothing) are provided implementations. - **Composition.** `CompositeSkipPolicy` combines several policies (skip if any votes to skip). **Registration and precedence.** You attach it with `new StepBuilder(...).faultTolerant().skipPolicy(myPolicy)`. Mixing `.skipPolicy(...)` with `.skip(...)/.skipLimit(...)` is confusing and generally the explicit `skipPolicy` takes over the decision — the recommendation is to put *all* skip logic inside the policy rather than splitting it. Retry still interacts: exceptions exhausted by retry become skip candidates and reach `shouldSkip`. **Statefulness gotcha.** `shouldSkip` can be called during the single-item write replay too. If your policy keeps its own counters, be careful not to double-count across rollback/replay — prefer to rely on the framework-supplied `skipCount` argument, which the framework manages correctly, rather than incrementing your own field. **A concrete example** (below) grants a large budget to parse errors but is strict on validation errors, throwing to fail the step once validation errors get out of hand. This is the canonical "different tolerance for different failure classes" pattern that a plain `skipLimit` cannot do. **When not to.** If a single exception whitelist and one global limit express your requirement, use the declarative API — a custom policy is more code to test and get right. Reach for it only when the decision is genuinely conditional.
- What does returning false from shouldSkip do?The item is not skipped; the original exception propagates and the step fails. It's how you say 'this failure is fatal'.
- Which built-in SkipPolicy do the declarative .skip()/.skipLimit() calls configure?LimitCheckingItemSkipPolicy — it holds the skippable exception classes and the limit and implements exactly the whitelist-plus-count check.
- Why should a custom policy prefer the skipCount argument over its own counter?shouldSkip may be re-invoked during rollback/replay; the framework-managed skipCount stays accurate, whereas a hand-rolled field can double-count.
saying these in an interview costs you the question
- Thinking SkipPolicy has many methods (it has one: shouldSkip)
- Believing a custom policy still respects a separately-set global skipLimit
- Maintaining a manual skip counter that double-counts on replay
- Claiming custom SkipPolicy is required for a simple single-type-with-limit case