Skip to content

File Writing ​

FileWriter ​

since v4.5.0

FileWriter is the core interface for writing export output. The framework calls its methods in the following order during an export:

writeStart(fields)              ← called once at the start (write headers)
  writeAndFlush(fields, batch1) ← called for each data batch (may be called many times)
  writeAndFlush(fields, batch2)
  ...
writeStop(fields)               ← called once at the end (cleanup)

If the concurrent export limit is exceeded before the export starts:

onTooManyRequests()             ← called instead of the above flow

Interface Definition ​

java
public interface FileWriter {

    /** Called at the start of export (e.g., write the header row) */
    void writeStart(List<ExportField> fields) throws IOException;

    /** Called for each batch of data */
    void writeAndFlush(List<ExportField> fields, List<?> dataList) throws IOException;

    /** Called at the end of export (cleanup) */
    void writeStop(List<ExportField> fields) throws IOException;

    /** Called when the concurrency limit is exceeded */
    void onTooManyRequests() throws IOException;

    /** Factory for creating FileWriter instances */
    interface Factory {
        FileWriter create(String filename) throws IOException;
    }
}

CsvFileWriter ​

CsvFileWriter is the built-in CSV writer. It outputs data in UTF-8 encoded CSV format.

Construction ​

java
// From an OutputStream (uses UTF-8 internally)
new CsvFileWriter(response.getOutputStream())

// From a Writer (you control the charset)
new CsvFileWriter(new OutputStreamWriter(fos, StandardCharsets.UTF_8))

File Extension Helper ​

java
CsvFileWriter.withFileExt("orders")       // → "orders.csv"
CsvFileWriter.withFileExt("orders.csv")   // → "orders.csv" (no duplicate extension)

CSV Escaping Rules ​

When a field value contains a comma (,), CsvFileWriter wraps the value in double quotes and escapes any embedded double quotes (" → ""):

No comma:     hello world     → hello world
With comma:   hello, world    → "hello, world"
With quotes:  say "hello"     → "say ""hello"""

Custom FileWriter ​

To support other formats such as Excel, implement FileWriter:

java
public class ExcelFileWriter implements FileWriter {

    private final OutputStream output;
    private Workbook workbook;
    private Sheet sheet;
    private int rowIndex = 0;

    public ExcelFileWriter(OutputStream output) {
        this.output = output;
    }

    @Override
    public void writeStart(List<ExportField> fields) throws IOException {
        workbook = new SXSSFWorkbook();
        sheet = workbook.createSheet("Data");
        Row header = sheet.createRow(rowIndex++);
        for (int i = 0; i < fields.size(); i++) {
            header.createCell(i).setCellValue(fields.get(i).getExName());
        }
    }

    @Override
    public void writeAndFlush(List<ExportField> fields, List<?> dataList) throws IOException {
        for (Object data : dataList) {
            Row row = sheet.createRow(rowIndex++);
            for (int i = 0; i < fields.size(); i++) {
                row.createCell(i).setCellValue(fields.get(i).text(data));
            }
        }
    }

    @Override
    public void writeStop(List<ExportField> fields) throws IOException {
        workbook.write(output);
        workbook.close();
    }

    @Override
    public void onTooManyRequests() throws IOException {
        throw new ExportException.TooManyRequests("Too many export requests.");
    }
}

FileWriter.Factory ​

FileWriter.Factory creates a new FileWriter instance each time an export is triggered. The framework calls Factory.create(filename) at the beginning of every export.

Custom Factory in SpringBoot ​

The default factory writes to the current HTTP response. Override it by declaring a FileWriter.Factory Bean:

java
@Bean
public FileWriter.Factory fileWriterFactory() {
    // Write to local disk instead
    return filename -> {
        File file = new File("/data/export/" + filename + ".csv");
        return new CsvFileWriter(new FileOutputStream(file));
    };
}

Formatter ​

Formatter formats a field value (or the result of an expr expression) into a string:

java
public interface Formatter {
    String format(String format, Object value);
}

The built-in Formatter.DEFAULT supports:

Value TypeFormattingExample
DateSimpleDateFormat"yyyy-MM-dd"
TemporalAccessor (LocalDate, etc.)DateTimeFormatter"yyyy-MM-dd HH:mm:ss"
NumberDecimalFormat"#,##0.00"
OtherString.format"%s (USD)"

Custom Formatter ​

java
@Bean
public Formatter myFormatter() {
    return (format, value) -> {
        if (value instanceof BigDecimal) {
            return new DecimalFormat(format).format(value) + " USD";
        }
        return Formatter.DEFAULT.format(format, value);
    };
}

Expresser ​

Expresser evaluates the expression defined in @Export.expr:

java
public interface Expresser {
    /** @ in expressions refers to the current field's value */
    String VALUE_REF = "@";

    Object evaluate(String expr, Object obj, Object value);
}
  • expr: the expression string;
  • obj: the current data object (fields accessible by name in the expression);
  • value: the current field's value (referenced as @ in the expression).

Auto-Configured

  • SpringBoot / Grails: a SpEL-based Expresser is auto-registered;
  • Solon: a SnEL-based Expresser is auto-registered;
  • Override by declaring a custom Expresser Bean.

Custom Expresser ​

java
@Bean
public Expresser myExpresser() {
    return (expr, obj, value) -> {
        Map<String, Object> context = new HashMap<>();
        context.put("_", value);   // _ refers to the current field value
        // add all object fields to context...
        return MVEL.eval(expr, context);
    };
}

Released under the Apache License