What happens when an ItemProcessor.process() returns null, and how is that different from throwing an exception?
answer
- null = filter, bumps filterCount
- Never reaches writer
- Exception = skip/retry/rollback path
- readCount = write + filter + skip
- Don't throw to drop valid records
basics
~10 sReturning null filters the item: it is silently dropped and never reaches the writer, and Spring Batch increments the step's filterCount. Throwing an exception instead triggers skip/retry/rollback handling — a very different, error path.
solid answer
~40 sIn an ItemProcessor, returning null is the official way to filter an item out. The framework discards it, it is never passed to the ItemWriter, and the step's filterCount metric is incremented (visible on StepExecution). This is a normal, expected outcome — think 'this record doesn't qualify'. Throwing an exception is the error path: depending on the step's fault-tolerance config it may roll back the chunk, retry, or be skipped (counting toward skipCount, subject to skipLimit). So null means 'valid decision to exclude', exception means 'something went wrong'. A common gotcha: if you only want to occasionally drop items, return null — do NOT throw. Also, because a filtered item still counts as read, readCount stays higher than writeCount, with the gap explained by filterCount.
code
java · 12 linespublic class ActiveOnlyProcessor implements ItemProcessor<Customer, Customer> {
@Override
public Customer process(Customer c) {
if (!c.isActive()) {
return null; // filtered out -> filterCount++, writer never sees it
}
if (c.getEmail() == null) {
throw new IllegalStateException("missing email"); // error path: skip/retry
}
return c;
}
}go deeper
Know that returning null drops the item.
Distinguish null-filter (filterCount) from exception (skip/retry/rollback) and explain the readCount vs writeCount gap.
Discuss retry re-invocation and CompositeItemProcessor short-circuit on null.
Reason about metric integrity, skip policy design, and why filtering keeps jobs green vs exceptions failing them.
**Filtering by returning null.** `ItemProcessor.process()` is annotated `@Nullable`. Returning `null` is a first-class signal to Spring Batch meaning *drop this item*. The framework: 1. Does **not** add the item to the chunk's output list, so `ItemWriter.write()` never sees it. 2. Increments `filterCount` on the `StepExecution` (retrievable via `stepExecution.getFilterCount()` and persisted in the batch metadata tables). 3. Continues normally with the next item — no rollback, no skip, no error. This is the intended mechanism for conditional inclusion: 'only load active customers', 'skip rows already imported', etc. It is a clean, expected control-flow outcome. **Throwing an exception is a different world.** If `process()` throws: - In a **non fault-tolerant** step, the exception propagates, the chunk transaction rolls back, and the step (and job) typically **fails**. - In a **fault-tolerant** step (`.faultTolerant()`), the exception may be **retried** (if the type is in `.retry(...)` up to `.retryLimit(...)`) and/or **skipped** (if in `.skip(...)` up to `.skipLimit(...)`). A skipped item increments `skipCount` (specifically process-skip), and a `SkipListener` can observe it. On retry, note the processor may be **re-invoked** for the same item after a rollback, so it must tolerate repeated calls unless `processorNonTransactional` semantics are considered. **Metrics relationship.** For a step: `readCount` counts items successfully read; `filterCount` counts items dropped by returning null (or filtered by the writer); `writeCount` counts items actually written. Roughly `readCount = writeCount + filterCount + skipCount(+ items in a failed chunk)`. Interviewers love asking why readCount != writeCount — filtering is the usual benign answer. **Gotchas.** - Do not conflate null-filtering with error handling. Using exceptions to drop unwanted-but-valid records pollutes skip metrics and can fail the job if no skip policy is set. - If a downstream processor in a `CompositeItemProcessor` returns null, the chain short-circuits — later processors are not called and the item is filtered. - Returning null is *not* the same as end-of-data; only the **reader** returning null signals end of input. A processor returning null just filters one item. - Under retry, throwing is expensive (rollback + re-read/re-process); prefer null-filtering for routine exclusion. **When to use which.** Return null for expected, rule-based exclusion. Throw an exception only for genuine failures (bad data you want skipped/logged, transient errors you want retried).
- After a run, readCount is 1000 but writeCount is 950 and skipCount is 0. Explain.The 50-item gap is almost certainly filterCount: 50 items had process() return null (or were filtered by the writer), so they were read but intentionally never written.
- Does a processor returning null signal end of the step, like a reader returning null?No. Only the ItemReader returning null means end of input. A processor returning null filters just that one item; reading and processing continue.
saying these in an interview costs you the question
- Saying returning null ends the step/job
- Claiming null-filtered items increment skipCount
- Using thrown exceptions as the normal way to drop unwanted records
- Thinking filtered items still reach the writer