Hardwood¶
A modern lightweight Java reader and writer for the Apache Parquet file format. Available as a Java library and a command-line tool.
Hardwood 1.0 is out!
Hardwood 1.0 is released — read the announcement blog post for the story behind the project and what it can do.
Why Hardwood¶
Hardwood gives applications fast and efficient support for reading and writing Parquet, without pulling in Hadoop, Avro, or the wider parquet-java dependency tree. It is built to be:
- Light-weight: Zero transitive dependencies beyond optional compression libraries (Snappy, ZSTD, LZ4, Brotli)
- Fast: Hardwood aims to be the fastest Parquet reader and writer for the JVM — the 1.0 read benchmarks are published
- Complete: Read and write support for flat and nested schemas, every logical type, every primitive type in current use, and the encodings and codecs in current use, with new format additions tracked as they land
- Scalable: Hardwood is multi-threaded at the core, pages are decoded in parallel, with cross-file prefetching for multi-file reads
- Embeddable: The Hardwood library can be used in GraalVM native binaries; WASM support coming soon (preview)
- Agent-friendly: Hardwood's CLI comes with a skill which lets your agents inspect and analyse Parquet files
- Compatible: A drop-in shim module facilitates migration from
parquet-java, with documented divergences where Hardwood applies stricter semantics (e.g. SQL three-valuednotEq)
Besides the core library, Hardwood provides a ready-to-use CLI for inspecting and analysing Parquet files, including an interactive TUI for exploring a file's schema, row groups, pages, and data.
Quick Example¶
Here's how you read a file with the row-based API:
import dev.hardwood.InputFile;
import dev.hardwood.reader.ParquetFileReader;
import dev.hardwood.reader.RowReader;
try (ParquetFileReader fileReader = ParquetFileReader.open(InputFile.of(path));
RowReader rowReader = fileReader.rowReader()) {
while (rowReader.hasNext()) {
rowReader.next();
long id = rowReader.getLong("id");
String name = rowReader.getString("name");
LocalDate birthDate = rowReader.getDate("birth_date");
Instant createdAt = rowReader.getTimestamp("created_at");
}
}
And here's how you write a file:
import dev.hardwood.OutputFile;
import dev.hardwood.writer.ParquetFileWriter;
import dev.hardwood.writer.RowWriter;
try (ParquetFileWriter writer = ParquetFileWriter.create(OutputFile.of(path), schema)) {
RowWriter rows = writer.rowWriter();
for (Person person : people) {
rows.writeRow(row -> row
.setLong("id", person.id())
.setString("name", person.name())
.setDate("birth_date", person.birthDate()));
}
}
Ready? Set up Hardwood in your project, then read your first file end-to-end. Alternatively, install the Hardwood CLI for working with Parquet files on the command line.
Prefer to learn by running code? The hardwood-examples repository collects small, self-contained examples — one per concept — that you can clone and run with a single command.
Status and Limitations¶
Hardwood 1.0 with read support is released and ready for production use. Support for writing Parquet files is under active development as of Hardwood 1.1.
The Hardwood library supports reading arbitrarily large Parquet files, provided individual column chunks are not larger than 2 GB (see Parquet file layout).
The interactive dive TUI currently caps S3 files at 2 GB.
Writing targets local files through OutputFile.of(Path); output to object storage is coming soon.
Roadmap¶
Forward-looking items tracked for post-1.0. None are committed to a specific release.
- Finalize
ColumnReaderAPI — stabilize the API for columnar access and move it out of "Experimental" state. (#522) - Writer extensions — object-store output, page-index and Bloom-filter writing, and parallel column encoding, on top of the write path described in The Write Model. (#9)
- Bloom filter predicate pushdown — use per-chunk bloom filters for equality-predicate skipping on high-cardinality columns, where min/max statistics can't help. (#105)
- Parquet Modular Encryption — read files encrypted under the Parquet Modular Encryption spec: encrypted footer, per-column keys, AES-GCM and AES-GCM-CTR. (#128)
- Apache Arrow interop —
ColumnReaderoutput as ArrowFieldVector/VectorSchemaRootfor zero-copy handoff to DuckDB, DataFusion, Pandas-via-JNI, and other Arrow-native consumers. (#153)
Getting help¶
- Questions, ideas, design discussion — GitHub Discussions. The best first stop for "how do I…", "is X possible…", or "what's the right way to…".
- Bug reports and feature requests — the GitHub issue tracker. Please check whether a similar issue already exists.
Articles, talks & podcasts¶
- Hardwood: A New Parser for Apache Parquet (project announcement)
- Hardwood 1.0: A Fast, Lightweight Apache Parquet Reader for the JVM (release announcement)
- Hardwood Promises High-Speed JVM Apache Parquet Processing with Zero Mandatory Dependencies (InfoQ article)
- Hardwood: Building a Parquet Parser From Scratch (With a Little Help From AI) (conference talk)
- GitHub Open Source Friday with Gunnar Morling (GitHub podcast)
- Chasing Efficient Java Development: From 1BRC to Developing Hardwood AI Natively (InfoQ podcast)