On the write side, how does FlatFileItemWriter turn a domain object into a line? Explain LineAggregator and FieldExtractor.
answer
- object → FieldExtractor → Object[] → LineAggregator → line
- Delimited vs Formatter aggregator (join vs String.format)
- BeanWrapperFieldExtractor.setNames → getters, order = columns
- PassThrough for already-a-line items
- header/footer callbacks, lineSeparator; no auto-escaping
basics
~10 sFlatFileItemWriter 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 sWriting 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 linesBeanWrapperFieldExtractor<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
Knows a LineAggregator turns the object into a line.
Names DelimitedLineAggregator + BeanWrapperFieldExtractor and the object→Object[]→line flow.
Distinguishes delimited vs formatter, controls column order, header/footer, and notes the no-auto-escaping gotcha.
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)