DEV Community

Cover image for Stop Wrestling With Apache POI—Meet Sheetz, the One-Liner Excel Library for Java
Chitrapradha Ganesan
Chitrapradha Ganesan

Posted on

Stop Wrestling With Apache POI—Meet Sheetz, the One-Liner Excel Library for Java

If you've ever processed an Excel file in Java, you know the drill.

Import Apache POI. Write 60 lines of boilerplate just to read a column header. Spend the next hour debugging a NullPointerException on row 14 because someone left a cell blank. Cry a little.

Sound familiar?

Meet Sheetz — an open-source Java library that collapses all of that pain into a single line of code:

List<Product> products = Sheetz.read("products.xlsx", Product.class);
Enter fullscreen mode Exit fullscreen mode

That's it. No boilerplate. No ceremony. Just data.


🤔 Why Another Excel Library?

Apache POI is powerful — but it was never designed for developer ergonomics. EasyExcel, FastExcel, and Poiji are better, but they all require significant setup before you can do anything useful.

Sheetz takes a different philosophy: zero config by default, full control when you need it.

Here's what you get out of the box:

  • 🚀 One-liner APISheetz.read(), Sheetz.write(), done
  • 📊 True SAX Streaming — process million-row files with constant ~10MB memory
  • 🔄 Auto Type Conversion — 19 built-in converters: dates, enums, BigDecimal, UUID, and more
  • 🧵 Thread-Safe — designed for concurrent use in multi-threaded apps
  • Built-in Validation — detailed error reporting with row/column context
  • 📝 Annotation Mapping@Column for custom headers, required fields, and defaults

⚡ Getting Started in 2 Minutes

Add the Dependency

Maven:

<dependency>
    <groupId>io.github.chitralabs.sheetz</groupId>
    <artifactId>sheetz-core</artifactId>
    <version>1.0.1</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Gradle:

implementation 'io.github.chitralabs.sheetz:sheetz-core:1.0.1'
Enter fullscreen mode Exit fullscreen mode

Define Your Model

public class Product {
    public String name;
    public Double price;
    public Boolean inStock;
    public LocalDate releaseDate;

    public Product() {} // Required no-arg constructor
}
Enter fullscreen mode Exit fullscreen mode

Read, Write, Done

// Read Excel
List<Product> products = Sheetz.read("products.xlsx", Product.class);

// Read CSV
List<Product> products = Sheetz.read("products.csv", Product.class);

// No model? No problem — read as maps
List<Map<String, Object>> data = Sheetz.readMaps("products.xlsx");

// Write Excel
Sheetz.write(products, "output.xlsx");

// Write CSV
Sheetz.write(products, "output.csv");
Enter fullscreen mode Exit fullscreen mode

🌊 Streaming Large Files (The Killer Feature)

This is where Sheetz really shines. Processing a file with 1 million rows? Use stream() and memory stays flat at ~10MB regardless of file size.

// Process row by row — constant memory
Sheetz.stream("huge-file.xlsx", Product.class)
    .forEach(product -> process(product));

// Process in batches — perfect for bulk DB inserts
Sheetz.stream("huge-file.xlsx", Product.class)
    .batch(1000)
    .forEach(batch -> database.bulkInsert(batch));

// Full Java Streams support
long count = Sheetz.stream("huge-file.xlsx", Product.class)
    .stream()
    .filter(p -> p.price > 100)
    .count();
Enter fullscreen mode Exit fullscreen mode

⚠️ Rule of thumb: For files over 100K rows, always use stream(). Loading everything into memory with read() on a million-row file is a fast track to OutOfMemoryError.


🏷️ Annotation-Based Mapping with @Column

Headers in your spreadsheet don't match your field names? Columns in a weird order? Need defaults for empty cells? @Column handles it all:

public class Product {
    @Column("Product Name")           // Map to a different header
    public String name;

    @Column(index = 1)                // Map by column index (0-based)
    public Double price;

    @Column(required = true)          // Fail validation if empty
    public String sku;

    @Column(defaultValue = "pending") // Default for empty cells
    public String status;

    @Column(format = "dd/MM/yyyy")    // Custom date format
    public LocalDate orderDate;

    @Column(converter = MoneyConverter.class) // Custom converter
    public BigDecimal amount;

    @Column(ignore = true)            // Skip this field entirely
    public String internalId;

    @Column(width = 20)               // Column width in chars (write only)
    public String description;
}
Enter fullscreen mode Exit fullscreen mode

✅ Built-in Data Validation

Stop writing manual row-parsing error handlers. Sheetz gives you structured validation results with precise row/column context:

ValidationResult<Product> result = Sheetz.validate("products.csv", Product.class);

System.out.println("Valid rows: "   + result.validCount());
System.out.println("Errors: "       + result.errorCount());
System.out.println("Success rate: " + result.successRate() + "%");

for (ValidationResult.RowError error : result.errors()) {
    System.out.println("Row "    + error.row()    +
                       ", Col '" + error.column() +
                       "': "     + error.message());
}

// Get only the clean rows
List<Product> validProducts = result.validRows();
Enter fullscreen mode Exit fullscreen mode

🏗️ Builder API for Full Control

Need to select a specific sheet? Set a custom header row? Auto-size columns? The fluent builder has you covered:

// Reader Builder
List<Product> products = Sheetz.reader(Product.class)
    .file("products.xlsx")
    .sheet("Inventory")    // By name
    .headerRow(1)          // Header on row 2 (0-based)
    .delimiter(';')        // For semicolon-delimited CSVs
    .read();

// Writer Builder
Sheetz.writer(Product.class)
    .data(products)
    .file("output.xlsx")
    .sheet("Products")
    .autoSize(true)        // Auto-fit column widths
    .freezeHeader(true)    // Freeze the header row
    .write();

// Multi-Sheet Workbook — this one's a gem 💎
Sheetz.workbook()
    .sheet("Products",  products)
    .sheet("Employees", employees)
    .sheet("Orders",    orders)
    .write("report.xlsx");
Enter fullscreen mode Exit fullscreen mode

🔧 Custom Converters

Got a money column prefixed with $? A legacy date format? Write a converter once and reuse it everywhere:

public class MoneyConverter implements Converter<BigDecimal> {
    @Override
    public BigDecimal fromCell(Object value, ConvertContext ctx) {
        String str = value.toString().replace("$", "").replace(",", "").trim();
        return new BigDecimal(str);
    }

    @Override
    public Object toCell(BigDecimal value) {
        return "$" + value.setScale(2, RoundingMode.HALF_UP);
    }
}

// Use per-field via annotation
@Column(converter = MoneyConverter.class)
public BigDecimal price;

// Or register globally for all BigDecimal fields
Sheetz.register(BigDecimal.class, new MoneyConverter());
Enter fullscreen mode Exit fullscreen mode

📦 Supported Types (19 Built-In Converters)

No more manual Integer.parseInt() or LocalDate.parse() calls. Sheetz auto-converts all of these:

Type Example Values
String Any text
Integer, Long, Double, Float 42, 3.14
BigDecimal, BigInteger 99.99, 999999999
Boolean true, yes, y, 1, on (case-insensitive)
LocalDate, LocalDateTime 2024-01-15, 2024-01-15 10:30:00
ZonedDateTime, Instant 2024-01-15T10:30:00Z
UUID 550e8400-e29b-41d4-a716-446655440000
Enum ACTIVE, active (case-insensitive)

📋 Format Support at a Glance

Format Read Write Streaming
.xlsx (Excel 2007+) ✅ SAX / SXSSF
.xls (Excel 97-2003)
.csv ✅ Buffered

⚙️ Global Configuration

Want to change the default date format or CSV encoding across your whole app? One config object to rule them all:

SheetzConfig config = SheetzConfig.builder()
    .dateFormat("dd/MM/yyyy")
    .dateTimeFormat("dd/MM/yyyy HH:mm")
    .trimValues(true)
    .skipEmptyRows(true)
    .streamingThreshold(10_000)       // Auto-stream above this row count
    .charset(StandardCharsets.ISO_8859_1)
    .build();

Sheetz.configure(config);
Enter fullscreen mode Exit fullscreen mode

🆚 How Does It Compare?

The sheetz-benchmarks repo has side-by-side JMH performance comparisons against Apache POI, EasyExcel, FastExcel, and Poiji—with full source code so you can run them yourself.

And if you want to see every feature in action, the sheetz-examples repo has 8 runnable demos covering end-to-end use cases.


🚀 Try It Now

git clone https://github.com/chitralabs/sheetz.git
cd sheetz
mvn clean install
Enter fullscreen mode Exit fullscreen mode

Requirements: Java 11+ · Apache POI 5.2.5 · OpenCSV 5.9
License: Apache 2.0


Sheetz is fresh out of the oven and actively looking for contributors, issues, and feedback. If you've ever wanted Excel/CSV processing that just works without a week of POI setup, give it a spin and drop a ⭐ if you find it useful.

👉 GitHub: https://github.com/chitralabs/sheetz

What's your current go-to library for Excel or CSV processing in Java? Drop it in the comments—I'm curious how the community handles this pain point!

Top comments (0)