skip to content

What is CompositeItemProcessor and how does chaining behave with respect to types, ordering, and null-filtering?

level: seniorimportance: should knowfreq 40%

answer

  1. Ordered delegates list
  2. Output of n feeds input of n+1
  3. Any null short-circuits -> filtered
  4. Cheap filters first
  5. Wildcard delegates -> ClassCastException at runtime

basics

~20 s

CompositeItemProcessor is an ItemProcessor that holds an ordered list of delegate processors and runs them in sequence, feeding each one's output into the next. If any delegate returns null, the chain stops and the item is filtered.

solid answer

~50 s

CompositeItemProcessor lets you compose several ItemProcessors into one pipeline for a single step. You set an ordered delegates list; process() calls them in order, passing each delegate's output as the next delegate's input. This is how you split responsibilities — validate, then enrich, then map to entity — into small, testable processors. Types flow through the chain: the first delegate's input is the composite's I, the last delegate's output is the composite's O, and each intermediate output type must be assignable to the next input. Crucially, if any delegate returns null, the composite short-circuits: remaining delegates are not called and the whole item is filtered (bumping filterCount). Ordering matters both for correctness and performance — put cheap filters early so expensive enrichment never runs on doomed items. Because delegates are type-erased in the list, a type mismatch compiles but fails at runtime with ClassCastException.

code

java · 10 lines
java
@Bean
ItemProcessor<CustomerCsv, CustomerEntity> compositeProcessor(
        ValidatingItemProcessor<CustomerCsv> validate,   // CustomerCsv -> CustomerCsv (or null to filter)
        EnrichProcessor enrich,                          // CustomerCsv -> EnrichedCustomer
        ToEntityProcessor toEntity) {                    // EnrichedCustomer -> CustomerEntity
    return new CompositeItemProcessorBuilder<CustomerCsv, CustomerEntity>()
            .delegates(validate, enrich, toEntity)  // run in this order
            .build();
}
// If validate.process() returns null, enrich and toEntity are skipped and the item is filtered.

go deeper

for a junior

Know it chains multiple processors into one.

for a middle

Explain output-feeds-next ordering and that it is one step-level processor.

for a senior

Detail null short-circuit, type flow/erasure, and ordering for performance.

for a principal

Compare with Classifier routing, discuss idempotency under retry and testability of decomposed stages.

**What it is.** `org.springframework.batch.item.support.CompositeItemProcessor<I, O>` is a built-in `ItemProcessor` implementation that delegates to an **ordered list** of other processors. You configure it with `setDelegates(List<? extends ItemProcessor<?, ?>>)` (or the builder `new CompositeItemProcessorBuilder<I,O>().delegates(p1, p2, p3).build()`). It exists so a step's transformation logic can be decomposed into small single-responsibility processors instead of one monolithic class. **Execution model.** `process(item)` iterates the delegates in list order. The output of delegate *n* becomes the input of delegate *n+1*. The value returned by the **last** delegate is what the composite returns to the framework (and thus what the writer receives). So it is a straightforward function-composition pipeline: `p3(p2(p1(item)))`. **Types.** The composite is declared `<I, O>` where `I` is the first delegate's input and `O` is the last delegate's output. Intermediate types can be anything, as long as each delegate's output is assignable to the next delegate's input. Because `setDelegates` takes a wildcard list, the compiler **cannot** check the internal type chain — a wiring mistake compiles cleanly but throws `ClassCastException` at runtime. Integration testing the whole step is the safeguard. **Null / filtering behaviour (key gotcha).** If **any** delegate returns `null`, the composite **short-circuits**: it stops immediately, does not invoke the remaining delegates, and returns `null` itself — so the item is filtered and `filterCount` increments. This means a filtering delegate placed early prevents later (possibly expensive) delegates from running. Conversely, if you *need* every stage to run, ensure earlier stages never return null unintentionally. **Ordering matters.** - *Correctness:* validation/normalization usually must precede enrichment and mapping. - *Performance:* put cheap, high-rejection filters first so costly enrichment (e.g. a remote lookup) is skipped for items that will be dropped. **Exceptions.** An exception thrown by any delegate propagates out of the composite exactly as a single processor's would — subject to the step's skip/retry configuration. Retry re-invokes the composite from the first delegate, so delegates must be idempotent/side-effect-free. **Alternatives / related.** For simple two-step composition you could also just call one processor from another, but `CompositeItemProcessor` gives declarative, individually-testable, Spring-wired stages. Don't confuse it with `ClassifierCompositeItemProcessor`, which routes each item to *one* processor chosen by a `Classifier` rather than running all of them in sequence. **When to use.** Reach for it when a step's per-item logic has distinct phases you want isolated, reused, or unit-tested independently — a very common clean-code pattern in batch jobs.

  • If the second of three delegates returns null, does the third run?
    No. The composite short-circuits on the first null: the third delegate is never called and the item is filtered, incrementing filterCount.
  • Why order matters beyond correctness?
    Performance: putting cheap, high-rejection filters first means expensive stages (e.g. remote enrichment) never run for items that will be dropped anyway.
  • How does CompositeItemProcessor differ from ClassifierCompositeItemProcessor?
    Composite runs ALL delegates in sequence (a pipeline). ClassifierCompositeItemProcessor uses a Classifier to route each item to exactly ONE processor instead of chaining them.

saying these in an interview costs you the question

  • Thinking all delegates still run after one returns null
  • Believing the compiler validates the intermediate type chain
  • Confusing it with ClassifierCompositeItemProcessor's routing behaviour
  • Assuming delegates run in parallel rather than in order

context