Every Java developer has written this code:
Workbook wb = new XSSFWorkbook(file);
Sheet sheet = wb.getSheetAt(0);
for (Row row : sheet) {
String name = row.getCell(0).getStringCellValue();
int qty = (int) row.getCell(1).getNumericCellValue();
// ... 20 more fields
}
Cell by cell. Column index by column index. Cast by cast. For every single spreadsheet your application touches.
What if you could do this instead?
SpreadsheetMapper mapper = new SpreadsheetMapper();
List<Employee> employees = mapper.readValues(file, Employee.class);
That's it. Same API as Jackson's ObjectMapper. Because a spreadsheet row IS a JSON object.
Introducing jackson-dataformat-spreadsheet
A Jackson extension module that treats XLSX/XLS as just another data format β like JSON, CSV, or XML.
GitHub: jackson-dataformat-spreadsheet
Reading
@DataGrid
public class Employee {
private String name;
private String department;
private int salary;
// getters, setters
}
SpreadsheetMapper mapper = new SpreadsheetMapper();
// Single row
Employee first = mapper.readValue(file, Employee.class);
// All rows
List<Employee> all = mapper.readValues(file, Employee.class);
// Specific sheet
SheetInput<File> input = SheetInput.source(file, "Payroll");
List<Employee> payroll = mapper.readValues(input, Employee.class);
Writing
List<Employee> employees = ...;
mapper.writeValue(file, employees, Employee.class);
That produces a proper XLSX file with headers and typed cells.
Nested Objects β The Killer Feature
Spreadsheets are flat. POJOs are not. Most Excel libraries force you to flatten everything manually. This library does it automatically:
βββββββ¬βββββββ¬ββββββββββ¬βββββββββββββββββ¬ββββββββββββββ¬βββββββββ
β ID β NAME β ZIPCODE β ADDRESS LINE 1 β DESIGNATION β SALARY β
βββββββΌβββββββΌββββββββββΌβββββββββββββββββΌββββββββββββββΌβββββββββ€
β 1 β John β 12345 β 123 Main St. β CEO β 300000 β
βββββββ΄βββββββ΄ββββββββββ΄βββββββββββββββββ΄ββββββββββββββ΄βββββββββ
@DataGrid
class Employee {
int id;
String name;
Address address;
Employment employment;
}
class Address {
String zipcode;
String addressLine1;
}
class Employment {
String designation;
long salary;
}
No configuration needed. The nested POJO structure defines the column layout. Read and write β both directions work.
How It's Built
This isn't a POI wrapper. It extends Jackson's streaming layer directly:
-
SheetParser extends ParserMinimalBaseβ pulls tokens from StAX -
SheetGenerator extends GeneratorBaseβ pushes cells via POI -
SpreadsheetFactory extends JsonFactoryβ creates parsers/generators
The read path parses OOXML XML directly via StAX. No XMLBeans, no SAX callbacks, no intermediate DOM. A pull parser feeding a pull parser β naturally composable.
Jackson (pull) SheetParser (pull) StAX (pull)
β β β
ββ nextToken() ββββββββΊββ next() βββββββββββββΊββ next()
βββ VALUE_STRING βββββββ€ββ CELL_VALUE βββββββββ€ββ START_ELEMENT
Performance
Benchmarked against popular alternatives on realistic data (100K rows, JMH):
ββββββββββββββββββββββββ¬ββββββββββββ¬ββββββββββ
β Library β Read Time β Memory β
ββββββββββββββββββββββββΌββββββββββββΌββββββββββ€
β FastExcel β 209 ms β 406 MB β
ββββββββββββββββββββββββΌββββββββββββΌββββββββββ€
β jackson-spreadsheet β 220 ms β 395 MB β
ββββββββββββββββββββββββΌββββββββββββΌββββββββββ€
β EasyExcel β 296 ms β 418 MB β
ββββββββββββββββββββββββΌββββββββββββΌββββββββββ€
β Apache POI UserModel β 1274 ms β 2347 MB β
ββββββββββββββββββββββββ΄ββββββββββββ΄ββββββββββ
Competitive with FastExcel on throughput, 5x faster than POI UserModel. The memory overhead is Jackson's data-binding cost β the price of getting type-safe POJOs instead of raw strings.
Annotations
Control the schema with @DataGrid and @DataColumn:
@DataGrid
class Product {
@DataColumn("Product Name")
String name;
@DataColumn(value = "Price", style = "currency")
double price;
@DataColumn(merge = OptBoolean.TRUE)
String category;
}
Getting Starts
Available on Maven Central:
<dependency>
<groupId>io.github.scndry</groupId>
<artifactId>jackson-dataformat-spreadsheet</artifactId>
<version>1.1.0</version>
</dependency>
Requirements
- Java 8+
- Jackson 2.14.0+
- Apache POI 4.1.1+ (Strict OOXML requires 5.1.0+)
Links
Feedback, issues, and stars welcome. If you've ever cursed at row.getCell(17).getStringCellValue(), this is for you.













