What is the ItemReader interface in Spring Batch and what is the contract of its read() method?
answer
- read() returns one item, null = end
- null is sentinel not data
- exception ≠ end-of-input
- framework loops read() to fill a chunk
- reader is stateful → cursor/position
basics
~10 sItemReader is the input side of a chunk-oriented step. Its single method read() returns one item at a time, or null when there is no more data, signalling the end of input.
solid answer
~40 sItemReader<T> is the read side of Spring Batch's chunk-oriented processing. It has one method, T read(), which returns exactly one item per call or null to signal that input is exhausted. The step framework calls read() repeatedly to fill a chunk (up to the configured chunk/commit-interval size), then hands the chunk to the ItemProcessor and ItemWriter inside a transaction. Because read() is one-at-a-time, a reader is inherently stateful: it must remember its position between calls. Returning null ends the step's reading loop. read() may throw a checked Exception; a thrown exception is not the same as null — null means 'clean end of data', an exception means 'error' and triggers skip/retry/fail handling.
code
java · 13 linespublic class CountingItemReader implements ItemReader<Integer> {
private final Iterator<Integer> it;
public CountingItemReader(List<Integer> data) {
this.it = data.iterator();
}
@Override
public Integer read() {
// one item per call; null when exhausted
return it.hasNext() ? it.next() : null;
}
}go deeper
Must know: read() returns one item, null ends input.
Should connect read() to the chunk loop and commit-interval.
Should mention statefulness, ItemStream, thread-safety implications.
Frames read() semantics against restartability and transaction boundaries.
### The interface ```java public interface ItemReader<T> { T read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException; } ``` `ItemReader<T>` is the **input** abstraction in Spring Batch's **chunk-oriented** processing model. A chunk-oriented `Step` runs a loop: 1. Call `read()` up to *N* times, where *N* is the **chunk size** (also called **commit-interval**), collecting items into a list. 2. Pass each item through the optional `ItemProcessor`. 3. Hand the whole list to the `ItemWriter.write(...)`. 4. Commit the surrounding transaction, update the `ExecutionContext`, and repeat. ### The read() contract - **One item at a time.** Each call returns a single `T`. The framework — not your code — decides how many times to call it before flushing a chunk. - **`null` means end-of-input.** When there is no more data, `read()` returns `null`. This is the *only* way to signal a clean end; the step's read loop stops and the step completes. - **`null` is a sentinel, not a data value.** You cannot have a legitimate `null` element in your stream — it always means 'exhausted'. - **Exceptions mean error, not end.** Throwing (e.g. `ParseException`, `FlatFileParseException`, `NonTransientResourceException`) triggers the step's skip / retry / fail policies. Never throw to mean 'done'. ### Statefulness Because reading is incremental, a reader keeps a **cursor/position** internally (current line number, ResultSet position, page number, XML fragment index). This is why most real readers also implement `ItemStream` (open/update/close) so their position can be saved to and restored from the `ExecutionContext` for **restart**. ### Built-in implementations Spring Batch ships many: `FlatFileItemReader` (CSV/fixed-width), `JdbcCursorItemReader` / `JdbcPagingItemReader` (JDBC), `JpaCursorItemReader` / `JpaPagingItemReader` (JPA), `StaxEventItemReader` (XML), `KafkaItemReader`, `MongoPagingItemReader`, etc. You rarely implement `ItemReader` by hand; you configure a provided one. ### Gotchas - Returning the same item forever (never returning `null`) causes an **infinite step**. - A reader is generally **not thread-safe**; in multi-threaded steps you must synchronize (e.g. `SynchronizedItemStreamReader`) or use a partitioned/cursor-safe reader. - `read()` is called *inside* the chunk transaction boundary in most configurations, so slow reads hold the transaction open.
- What happens if read() never returns null?The step's read loop never terminates, so the step runs forever (or until it exhausts memory/times out). null is the only clean stop signal.
- How does throwing an exception from read() differ from returning null?null = clean end of data, step completes normally. An exception triggers the step's error handling (skip, retry, or fail) depending on the fault-tolerant configuration; it never means 'done'.