# fastexcel writes xlsx by piping XML instead of holding a workbook in memory

> fastexcel is a Java library for generating and reading large Excel workbooks, built around the idea that only the elements you need should be accumulated in memory while the XML goes straight to the output stream. It trades feature breadth, with basic style support and no charts, for a smaller memory footprint and per-worksheet multithreading.

**dhatim/fastexcel** — Generate and read big Excel files quickly

- Repository: https://github.com/dhatim/fastexcel
- Stars: 914 · Forks: 149
- Language: Java
- License: NOASSERTION
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/dhatim-fastexcel

## The problem is memory, and POI's streaming mode has its own limits

The README opens with a positioning claim that is worth reading carefully, because it explains every design choice that follows.

There are not many alternatives when you have to generate xlsx Excel workbooks in Java. The most popular one, Apache POI, includes many features, but when it comes down to huge worksheets it quickly becomes a memory hog.

The obvious reply is POI's streaming API, and the README explains why that is not a complete answer. The streaming API may mitigate the problem but it introduces several limitations, and the project lists three of them.

Its sliding window mechanism prevents you from accessing cells above the current writing position. It writes stuff to a temporary file. And it comes with an overhead on the file size because shared strings are disabled by default, with the note that enabling shared strings is likely to consume much more heap if you deal with string values.

Those three points are the design brief for fastexcel. The sliding window limitation is the subtlest and the most important: in a streaming writer you cannot go back and amend a cell you already wrote, which rules out a large class of formatting workflows. The temporary file is a deployment annoyance in containers. And the shared strings default is a direct trade between file size and memory that every user has to make deliberately.

So fastexcel is not claiming to be a faster POI. It is claiming to be a writer with a different shape, and the benchmark section in the README makes the comparison concrete.

## What the benchmark actually measured, and what it left out

The benchmark is a simple test: a workbook with a single worksheet containing 100,000 rows and 4 columns.

The results stated are that Apache POI in non-streaming mode is about 10 times slower than fastexcel and uses 12 times more heap memory. The streaming API of Apache POI is almost on par with fastexcel in terms of performance and uses less heap just because it keeps only 100 rows in memory, and the README points back to the limitations paragraph for the caveat.

That is the honest shape of the claim. fastexcel wins clearly against POI's default API, and roughly ties POI's streaming API on speed while using more heap to get there. The README is not claiming a victory over the streaming mode, and it explains why: the streaming API is fast precisely because it holds only a small window, at the cost of the three limitations listed earlier.

The measurement note is also specific. Heap memory usage is measured just before flushing the workbook to the output stream, which is the moment when the accumulated elements are at their largest, before the XML is written out. That is the honest place to measure, since measuring after the flush would show a number that tells you nothing.

Two charts accompany the results, one for generation time and one for heap memory usage.

The methodology is thin by the standards of a serious benchmark. One shape of workbook, one row count, one column count, no mention of whether the values are numbers or strings, and no description of the machine or the JVM flags. Treat the ten times and twelve times as an indication of the shape of the difference rather than as a number to plan capacity around.

## Three key points that define the library

The README states the design in three points, and they are the whole architecture.

The first is a limited set of features, described as basic style support with no graph support yet, and a very simple API. That is a deliberate trade. A writer that supports a small, well-understood set of cell operations can be implemented without holding a full object model of the workbook in memory.

The second is a reduced memory footprint and high performance by accumulating only necessary elements, with the XML stuff piped to the output stream at the end. That last clause is the key. The workbook is not serialised cell by cell as you write it. Instead, the cells you write are accumulated in memory, and the underlying XML is piped to the output stream at the end. That is why the cells above the current writing position are still reachable, which is exactly what POI's sliding window forbids.

The third is multithreading support, where each worksheet in the workbook can be generated by a different thread, while fully supporting shared strings and styles. This is the feature that has no equivalent in the POI comparison, and it comes from the accumulation model: because each worksheet buffers its own elements rather than writing a single serialised stream, independent worksheets can be built concurrently and flushed afterwards.

That last point is the reason to pick fastexcel over a hand-rolled SAX writer. Writing xlsx from scratch with an XML stream API is possible but tedious, and it gives up style and shared string handling unless you implement them. fastexcel gives you the tedious part and keeps the memory profile.

The repository structure reflects the split into two modules, fastexcel-writer/ and fastexcel-reader/, with the writer as the primary subject of the documentation.

## Adding the dependency and writing the first worksheet

The prerequisites are Java 8 or later, built with Maven, and a dependency in your POM.

```xml
<dependency>
    <groupId>org.dhatim</groupId>
    <artifactId>fastexcel</artifactId>
    <version>0.20.2</version>
</dependency>
```

The version in the README matches the latest published release, 0.20.2. The group is org.dhatim and the artifact is fastexcel.

The first example creates a workbook with a single worksheet and a few cells of the supported data types.

```java
try (OutputStream os = ..., Workbook wb = new Workbook(os, "MyApplication", "1.0");) {
    Worksheet ws = wb.newWorksheet("Sheet 1");
    ws.value(0, 0, "This is a string in A1");
    ws.value(0, 1, new Date());
    ws.value(0, 2, 1234);
    ws.value(0, 3, 123456L);
    ws.value(0, 4, 1.234);
}
```

Three things to notice. The workbook takes an output stream and an application name and version, which end up as workbook metadata. Cells are addressed by row and column index, both zero-based in this example, so cell A1 is value(0, 0). And the types written are a String, a java.util.Date, an int, a long and a double.

The try-with-resources block is the shape of the whole API. You write cells and styles in any order you like, and the file is written when the workbook is closed. Nothing is emitted to the stream until then, which is the accumulation model in action.

The javadoc for the last release is published and linked from the README, so the full method set is there for the version you are pinning.

## Styles, ranges and the conditional formatting that is supported

The styling API is a builder with a set() call to commit, and the examples show how much is actually available.

A single cell gets bold with a predefined fill pattern.

```java
ws.style(0, 0).bold().fill(Fill.GRAY125).set();
```

A date cell gets a number format.

```java
ws.style(0, 0).format("yyyy-MM-dd H:mm:ss").set();
```

And a cell can carry a conditional formatting rule of expression type.

```java
ws.style(0, 0).fillColor("FF8800").set(new ConditionalFormattingExpressionRule("LENB(A1)>1", true));
```

Ranges work the same way, with a range builder that ends in set() too.

```java
ws.range(0, 0, 10, 10).style().horizontalAlignment("center").italic().set();
```

The range API also covers merging, alternating row shading and shading every Nth row.

```java
ws.range(0, 0, 10, 10).style().shadeAlternateRows(Color.GRAY2).set();
```

That last example, shade every fifth row, is the kind of detail that tells you the range model is a real one rather than a thin wrapper over individual cells.

Other worksheet features shown are tab colour, keeping the sheet in the active tab, paper size and page orientation, margins, and freeze panes. There is also a global default font setter, wb.setGlobalDefaultFont with a family name and a size.

Formulas are supported with a caveat that matters: cells with a formula do not have a value in the generated workbook. There is also a neat trick with range toString, which lets you build a formula reference without hardcoding cell addresses.

```java
ws.formula(10, 0, "SUM(" + ws.range(0, 0, 9, 0).toString() + ")");
```

## Shared strings versus inline strings, and the memory trade behind them

One decision in the README deserves more attention than the rest, because it is where file size and memory pull in opposite directions.

The value function with a string sets the value as a shared string. If there are many duplicate strings, using a shared string can be very efficient in terms of file size. However, if there are many distinct strings, using shared strings can consume a large amount of memory to share these strings. Therefore you may choose not to share strings to use less memory, even if the file size will be large. In that case you can use inline strings.

```java
ws.inlineString(0, 0, "Inline String");
```

So the choice is per-cell, not per-workbook, which is the right granularity. A column of repeated country names and a column of free-text descriptions behave very differently, and a library that forces one choice for the whole file gets one of the two cases wrong.

This also explains something about the comparison with POI. POI's streaming API disables shared strings by default, which the README frames as a file size overhead. fastexcel supports shared strings fully, including across worksheets generated by different threads, so the file stays small without giving up the multithreading model. The cost is the memory of the shared string table, and inline strings are the escape valve.

Other content features shown in the README include hyperlinks, to a URL or to a local file, and Excel tables with a display name, a name, a style and a show-last-column flag.

```java
ws.range(0, 0, 5, 2).createTable()
                        .setDisplayName("TableDisplayName")
                        .setName("TableName")
                        .styleInfo()
                        .setStyleName("TableStyleMedium1")
                        .setShowLastColumn(true);
```

## Licence, releases and the reader side

The licence field on the repository is a custom type that GitHub cannot categorise, so the terms are in the LICENSE file at the root, and the README does not summarise them in prose. If you are shipping a product that embeds fastexcel, read that file rather than the badge.

The release history is steady and the versions carry their own names in a slightly unusual form: 0.20.2 released 2026-06-08, 0.20.1 on 2026-05-19, and 0.20.0 on 2026-02-28. The last push was on 2026-10-14 and the repository is not archived, so the tree is current even though the newest tag is from the summer. The zero-series version numbers signal that the project has not yet declared itself stable in the usual semantic versioning sense.

The repository holds two modules. fastexcel-writer/ is the subject of nearly all the README, and fastexcel-reader/ is the reading side, which the description covers as generate and read big Excel files quickly but which the README examples do not document. If your problem is reading rather than writing, the docs here will not get you far and you will need the javadoc.

The build tooling is visible in the tree. There is a pom.xml at the root for the Maven build, a renovate.json, which means dependencies are updated automatically, a doc/ directory holding the benchmark charts, and an e2e/ directory for end to end tests. The CI badge points at a build workflow and a coverage badge points at coveralls, so both compilation and test coverage are tracked.

One structural point: the writer and the reader are separate Maven artifacts under the same group, which means you can take the writer alone and avoid pulling in parsing code you will not use.

## Conclusion

Adopt fastexcel if you generate multi-hundred-megabyte xlsx files in Java and the current writer's memory profile is your bottleneck, especially when each worksheet can be produced by a separate thread. Do not adopt it if you need charts, rich cell features or anything beyond the documented set, because the library explicitly limits its feature set, and check first whether your strings are mostly duplicates, since shared strings are where the file size win comes from and they cost memory. Before you build on it, run the reader side against a file you care about, because the repository ships fastexcel-writer and fastexcel-reader as separate modules and the writer's behaviour is the part most of the documentation covers.

## FAQ

### What is fastexcel and what problem does it solve?

fastexcel is a Java library for generating and reading big Excel files. It exists as an alternative to Apache POI for huge worksheets, where POI quickly becomes a memory hog, and it accumulates only necessary elements while piping the XML to the output stream at the end.

### How do I add fastexcel to a Maven project?

Add the org.dhatim dependency with artifactId fastexcel and the version you want, 0.20.2 in the README example. Java 8 or newer is required.

### How does fastexcel compare with Apache POI?

In the README benchmark of a single worksheet with 100,000 rows and 4 columns, Apache POI in non-streaming mode is about 10 times slower than fastexcel and uses 12 times more heap memory. POI's streaming API is almost on par in performance and uses less heap because it keeps only 100 rows in memory, at the cost of a sliding window, a temporary file and disabled shared strings by default.

### When should I use inline strings instead of shared strings in fastexcel?

Shared strings keep the file small when many strings are duplicates, but consume a large amount of memory when the strings are mostly distinct. In that case you can use inline strings to use less memory even though the file size will be large.

### Does fastexcel support charts?

No. The README describes a limited set of features, basic style support and no graph support yet, together with a very simple API.

### Can fastexcel write worksheets from multiple threads?

Yes. Each worksheet in the workbook can be generated by a different thread, while fully supporting shared strings and styles.

## Sources

- [dhatim/fastexcel on GitHub](https://github.com/dhatim/fastexcel)
- [Issues](https://github.com/dhatim/fastexcel/issues)
- [README](https://github.com/dhatim/fastexcel/blob/master/README.md)
- [Releases](https://github.com/dhatim/fastexcel/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/dhatim-fastexcel
