jackson-dataformat-spreadsheet

May 7, 2026 · View on GitHub

Build Maven Central License

A Java library (Jackson extension) for reading and writing Excel spreadsheets (XLSX/XLS) as POJOs.

Map spreadsheet rows to Java objects with ObjectMapper — the same API you use for JSON, CSV, and XML. No cell-level iteration, no column index counting, no manual type casting.

What It Does

SpreadsheetMapper mapper = new SpreadsheetMapper();

// Read Excel to POJOs
List<Employee> employees = mapper.readValues(file, Employee.class);

// Write POJOs to Excel
mapper.writeValue(file, employees, Employee.class);

That's it. If you know Jackson, you know this library.

When To Use This

  • You need to read XLSX/XLS files into Java objects without writing cell-by-cell parsing code
  • You need to write Java objects to Excel with headers, types, and styling
  • You want Jackson ecosystem integration (custom deserializers, mix-ins, modules)
  • You have nested object structures that need to flatten into spreadsheet columns
  • You need streaming performance for large files (100K+ rows)

Installation

Available on Maven Central:

Maven:

<dependency>
    <groupId>io.github.scndry</groupId>
    <artifactId>jackson-dataformat-spreadsheet</artifactId>
    <version>1.6.1</version>
</dependency>

Gradle:

implementation "io.github.scndry:jackson-dataformat-spreadsheet:1.6.1"

Requirements

  • Java 8+
  • Jackson 2.14.0+
  • Apache POI 4.1.1+ (Strict OOXML requires 5.1.0+)

Quick Start

Define Your Model

@DataGrid
public class Product {
    private String name;
    private int quantity;
    private double price;
    // constructors, getters, setters
}

Read from Excel

SpreadsheetMapper mapper = new SpreadsheetMapper();

// Single object (first row)
Product product = mapper.readValue(file, Product.class);

// All rows
List<Product> products = mapper.readValues(file, Product.class);

// Specific sheet
SheetInput<File> input = SheetInput.source(file, "Products");
List<Product> products = mapper.readValues(input, Product.class);

Write to Excel

List<Product> products = Arrays.asList(
    new Product("Apple", 10, 1.50),
    new Product("Banana", 20, 0.80));

// Single object
mapper.writeValue(file, products.get(0), Product.class);

// All rows
mapper.writeValue(file, products, Product.class);

// Specific sheet
SheetOutput<File> output = SheetOutput.target(file, "Products");
mapper.writeValue(output, products, Product.class);

Nested Objects

Flat spreadsheets map to nested POJOs automatically:

idnamezipcodecitytitlesalary
1Alice12345SeoulSRE80000
@DataGrid
class Employee {
    int id;
    String name;
    Address address;    // flattened: zipcode, city
    Employment employment; // flattened: title, salary
}

No configuration needed. Read and write — both directions work.

How It Compares

Performance (100K rows, mixed types, shared string table)

Read:

LibraryTimeMemory
jackson-spreadsheet198 ms378 MB
FastExcel212 ms428 MB
Fesod279 ms400 MB
Poiji843 ms2876 MB
Apache POI1198 ms2333 MB

Write:

LibraryTimeMemory
jackson-spreadsheet150 ms191 MB
FastExcel166 ms156 MB
Apache POI283 ms207 MB
Fesod337 ms480 MB

Fastest read and write throughput at 100K rows. See BENCHMARK.md for full results.

Feature Comparison

Featurejackson-spreadsheetApache POIFesodFastExcel
POJO data bindingYesNoYesNo
Nested object supportYesNoNoNo
Jackson ecosystemYesNoNoNo
Streaming readYes¹Event APIYesYes
Streaming writeYes¹SXSSFYesYes
Cell stylingYesYesYesNo
XLSX read/writeYesYesYesYes
XLS read/writeYesYesNoNo
Annotation mappingYesNoYesNo

¹ XLSX read streams via StAX, write via StringBuilder over a POI scaffold; XLS uses in-memory POI workbook (HSSF has no streaming API).

Key Features

Annotations

@DataGrid
class Product {
    @DataColumn("Product Name")
    String name;

    @DataColumn(value = "Price", style = "currency")
    double price;

    @DataColumn(merge = OptBoolean.TRUE)
    String category;
}

Streaming for Large Files

SpreadsheetReader reader = mapper.sheetReaderFor(Product.class);
try (SheetMappingIterator<Product> iter = reader.readValues(input)) {
    while (iter.hasNext()) {
        Product p = iter.next();
        SheetLocation loc = iter.getCurrentLocation();
        // loc.getRow(), loc.getColumn() — zero-based cell position
    }
}

Cell Styling

StylesBuilder styles = new StylesBuilder()
    .cellStyle("currency")
        .dataFormat("#,##0.00")
        .font().bold().end()
        .end();

SpreadsheetMapper mapper = SpreadsheetMapper.builder()
    .stylesBuilder(styles)
    .build();

Sheet-Level Features

GridConfigurer adds conditional formatting, freeze pane, and auto filter on top of the data grid — without dropping into POI Sheet/Cell code.

SpreadsheetMapper mapper = SpreadsheetMapper.builder()
    .stylesBuilder(styles)
    .gridConfigurer(new GridConfigurer()
        .freezePane(0, 1)
        .autoFilter()
        .conditionalFormatting("score",
            greaterThanOrEqual(80).style("highlight")))
    .build();

Conditional formatting rules reference columns from the model class and styles from StylesBuilder — both name-based and resolved at write time. Static-import the factory methods from ConditionalFormats for fluent chaining; see the GUIDE for typed operators, multi-rule columns, and color scale.

Configuration

SpreadsheetMapper mapper = SpreadsheetMapper.builder()
    .origin("B2")           // start at B2 instead of A1
    .useHeader(false)        // no header row
    .enable(SheetParser.Feature.BREAK_ON_BLANK_ROW)
    .build();

Excel Date Handling

Built-in conversion between Java date types and Excel serial numbers. Registered by default — no setup needed.

Supported: Date, Calendar, LocalDate, LocalDateTime

Architecture

Not a POI wrapper. Extends Jackson's streaming layer directly:

  • SheetParser extends ParserMinimalBase — StAX pull parser
  • SheetGenerator extends GeneratorBase — streaming cell writer
  • SpreadsheetFactory extends JsonFactory — format detection

The XLSX path bypasses POI's User Model by default — the read path parses OOXML XML directly via StAX, the write path streams XML via StringBuilder with a POI scaffold for package metadata.

See ARCHITECTURE.md for design decisions and data flow diagrams.

Documentation

FAQ

Q: How is this different from Apache POI? POI gives you cells. This gives you POJOs. You define a class with @DataGrid, and mapper.readValues() returns typed objects. No row.getCell(0).getStringCellValue().

Q: How is this different from Fesod? Fesod has its own API. This extends Jackson's ObjectMapper, so you get the full Jackson ecosystem — custom deserializers, mix-ins, modules, polymorphic types.

Q: Does it support nested objects? Yes. Nested POJOs automatically flatten to columns on write and reconstruct on read. No configuration needed.

Q: How does performance compare? Fastest read and write throughput at 100K rows. 6x faster read than Apache POI. Default writer is 10% faster than FastExcel. See BENCHMARK.md.

Q: What Excel formats are supported? XLSX (OOXML) and XLS (legacy). XLSX uses StAX streaming; XLS uses POI object model.

Q: Is it production-ready? Yes. Version 1.6.1 on Maven Central. Java 8+, Jackson 2.14+, POI 4.1.1+. Listed as a community data format module in the FasterXML jackson repository.

Q: Is the mapper thread-safe? The mapper instance is reusable across threads once configured (same rule as Jackson's ObjectMapper). Concurrent calls with File / InputStream / OutputStream inputs are safe — the library opens an isolated Workbook per call. If you pass a Sheet directly, POI's Workbook/Sheet are not thread-safe, so each thread needs its own.

License

Apache License 2.0