skip to content

On the write side, how does FlatFileItemWriter turn a domain object into a line? Explain LineAggregator and FieldExtractor.

level: seniorimportance: must knowfreq 55%

answer

  1. object → FieldExtractor → Object[] → LineAggregator → line
  2. Delimited vs Formatter aggregator (join vs String.format)
  3. BeanWrapperFieldExtractor.setNames → getters, order = columns
  4. PassThrough for already-a-line items
  5. header/footer callbacks, lineSeparator; no auto-escaping

basics

~10 s

FlatFileItemWriter uses a LineAggregator to turn each object into a String line. A DelimitedLineAggregator (or FormatterLineAggregator) pulls values out via a FieldExtractor — usually BeanWrapperFieldExtractor with property names — then joins or formats them.

solid answer

~40 s

Writing is the mirror of reading. FlatFileItemWriter<T> calls a LineAggregator<T>'s aggregate(item) to produce one String per object; the writer appends the line separator. The common aggregators are DelimitedLineAggregator (joins fields with a delimiter) and FormatterLineAggregator (formats with a String.format pattern for fixed-width output). Both delegate value extraction to a FieldExtractor<T>, whose Object[] extract(item) returns the field values in order. BeanWrapperFieldExtractor reads named JavaBean properties (setNames("firstName","lastName","age")) via a BeanWrapper; PassThroughFieldExtractor emits the object as-is. So the pipeline is: object → FieldExtractor → Object[] → LineAggregator → String line. You can also implement LineAggregator directly for full control, and use a headerCallback/footerCallback on the writer for header and footer lines.

code

java · 14 lines
java
BeanWrapperFieldExtractor<Person> extractor = new BeanWrapperFieldExtractor<>();
extractor.setNames(new String[]{"firstName", "lastName", "age"}); // getters, column order

DelimitedLineAggregator<Person> aggregator = new DelimitedLineAggregator<>();
aggregator.setDelimiter(",");
aggregator.setFieldExtractor(extractor);

FlatFileItemWriter<Person> writer = new FlatFileItemWriterBuilder<Person>()
    .name("personWriter")
    .resource(new FileSystemResource("out.csv"))
    .lineAggregator(aggregator)
    .headerCallback(w -> w.write("firstName,lastName,age"))
    .build();
// Person("John","Smith",42) -> "John,Smith,42"

go deeper

for a junior

Knows a LineAggregator turns the object into a line.

for a middle

Names DelimitedLineAggregator + BeanWrapperFieldExtractor and the object→Object[]→line flow.

for a senior

Distinguishes delimited vs formatter, controls column order, header/footer, and notes the no-auto-escaping gotcha.

for a principal

Designs robust CSV escaping strategies, cross-platform separators, and custom aggregators for complex output contracts.

## The write-side pipeline (mirror of reading) `FlatFileItemWriter<T>` is an `ItemWriter` that writes objects out as text lines. The transformation is driven by a `LineAggregator`. ``` T item ──► LineAggregator.aggregate(item) ──► String line ──► file + lineSeparator ``` ### LineAggregator — object to line `LineAggregator<T>`: `String aggregate(T item)`. Implementations: - **`DelimitedLineAggregator<T>`** — joins the extracted field values with a delimiter (default comma) into a delimited line. Configure `setDelimiter(...)`. - **`FormatterLineAggregator<T>`** — uses a `String.format`-style `setFormat("%-10s%-10s%3d")` pattern to produce **fixed-width** output. This is the writing counterpart of `FixedLengthTokenizer`. - **`PassThroughLineAggregator<T>`** — just calls `toString()` on the item. - A **custom `LineAggregator`** when you need bespoke line construction. ### FieldExtractor — object to Object[] `DelimitedLineAggregator` and `FormatterLineAggregator` don't know how to read your object; they delegate to a `FieldExtractor<T>`: `Object[] extract(T item)` returns the field values **in the order** they should appear in the line. Implementations: - **`BeanWrapperFieldExtractor<T>`** — the write-side counterpart of `BeanWrapperFieldSetMapper`. Call `setNames("firstName","lastName","age")`; it reads those JavaBean **getters** via a `BeanWrapper` and returns their values in that order. Requires getters for the named properties. - **`PassThroughFieldExtractor<T>`** — returns the item itself (single-element array) — handy when the item is already a String/array/`FieldSet`. ### Full example flow `Person{firstName,lastName,age}` → `BeanWrapperFieldExtractor.setNames("firstName","lastName","age")` yields `["John","Smith",42]` → `DelimitedLineAggregator` with delimiter `,` → `"John,Smith,42"` → writer appends the platform/`setLineSeparator` newline. ### Writer-level details & gotchas - **Header/footer**: `setHeaderCallback(FlatFileHeaderCallback)` / `setFooterCallback(FlatFileFooterCallback)` write header/trailer lines (column titles, record counts). - **Line separator**: `setLineSeparator(...)` — important for cross-platform output. - **Append vs. overwrite / restart**: `setAppendAllowed` / `setShouldDeleteIfExists` control file reuse; on restart the writer truncates back to the last committed position (that lifecycle detail belongs to the itemwriter leaf). - **Order matters**: the `names` order in `BeanWrapperFieldExtractor` defines column order; it is independent of the read-side names. - **Escaping**: `DelimitedLineAggregator` does not quote/escape values containing the delimiter by default — a value with a comma will corrupt a CSV; handle via a custom extractor/aggregator if needed. ### When to use which aggregator - CSV/TSV/pipe output → `DelimitedLineAggregator` + `BeanWrapperFieldExtractor`. - Fixed-width/legacy output → `FormatterLineAggregator` + `BeanWrapperFieldExtractor`. - Object already a line / trivial → `PassThroughLineAggregator`.

  • Which aggregator would you use to produce fixed-width output, and how do you define the layout?
    FormatterLineAggregator, with setFormat(...) using a String.format pattern like "%-10s%-10s%3d" that pads/aligns each field to its column width.
  • How is column order determined when writing with BeanWrapperFieldExtractor?
    By the order of the names array passed to setNames — the extractor returns values in that order, and the aggregator joins/formats them in that same order.
  • Does DelimitedLineAggregator escape values that contain the delimiter?
    No — by default it just joins with the delimiter, so a value containing a comma corrupts the CSV. You need a custom extractor/aggregator or quoting logic to handle it.

saying these in an interview costs you the question

  • Confusing LineAggregator (object→line) with LineTokenizer (line→fields)
  • Thinking BeanWrapperFieldExtractor uses setters — it reads getters
  • Assuming DelimitedLineAggregator auto-quotes embedded delimiters
  • Believing FieldExtractor determines the delimiter (that's the aggregator)

context