hardwood-hq/hardwood

A fast minimal dependency implementation of Apache Parquet

371

stars

852

commits

Java

primary language

Sep 11, 2026

updated

hardwood.dev/
apache-parquet
columnar-format
hardwood
parquet
parquet-tools
performance

README

Hardwood logo

Hardwood

A reader and writer for the Apache Parquet file format, optimized for minimal dependencies and great performance. Available as a Java library and a command-line tool.

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 — see the read benchmarks
  • 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: Supports all Parquet files which the canonical parquet-java library supports, and a drop-in shim module eases migration from it, with documented divergences where Hardwood applies stricter semantics (e.g. SQL three-valued notEq)

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.

Latest version: 1.0.0.Final, 2026-06-25

Documentation

Full documentation is available at hardwood.dev.

Quick Start

<dependency>
    <groupId>dev.hardwood</groupId>
    <artifactId>hardwood-core</artifactId>
    <version>1.0.0.Final</version>
</dependency>

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()));
    }
}

See the Getting Started guide for detailed setup instructions.

Limitations

  • Local files (memory-mapped via InputFile.of(Path)) may be arbitrarily large; each individual column chunk must be at most 2 GB of compressed data.
  • In-memory (InputFile.of(ByteBuffer)) and object-store sources are limited to 2 GB per file. Split larger datasets across multiple files and read them with Hardwood.openAll(...) or ParquetFileReader.openAll(...).
  • Writing is under active development as of Hardwood 1.1, and is documented in the development docs until 1.1 is released. It targets local files via OutputFile.of(Path); output to object storage is coming soon.

Repository Documentation

DocumentPurpose
ARCHITECTURE.mdHigh-level architecture and module layout.
CONTRIBUTING.mdHow to contribute: workflow, commit format, PR expectations.
ROADMAP.mdImplementation status, roadmap, and milestones.
NATIVE_BUILD.mdHow the GraalVM native CLI build works.
PERFORMANCE.mdBenchmark results and how to run performance tests.
TESTING.mdManual testing recipes (e.g. S3 via s3proxy).
RELEASING.mdRelease process.

Articles, Talks & Podcasts

See hardwood.dev for a list of articles, talks, and podcasts about Hardwood.


Contributing

Contributions are welcome! See CONTRIBUTING.md for the full guide — how to find work, the issue-first workflow, commit message format, and PR expectations.

LLM-assisted contributions are welcome, but vibe coding — accepting AI-generated changes without understanding them — is not. The aspiration is a high-quality, maintainable, performant, safe codebase.

See ROADMAP.md for the detailed implementation status, roadmap, and milestones.


Build

This project requires Java 25 or newer for building (to create the multi-release JAR with Java 22+ FFM support). The resulting JAR runs on Java 21+ (libdeflate support requires Java 22+).

Docker must be running for the build to succeed, as the test suite uses Testcontainers to spin up services (e.g. S3 integration tests). If Docker is not available, the build will fail during the test phase for these tests.

It comes with the Apache Maven wrapper, i.e. a Maven distribution will be downloaded automatically, if needed.

Run the following command to build this project:

./mvnw clean verify

On Windows, run the following command:

mvnw.cmd clean verify

Pass the -Dquick option to skip all non-essential plug-ins and create the output artifact as quickly as possible:

./mvnw clean verify -Dquick

Run the following command to format the source code and organize the imports as per the project's conventions:

./mvnw process-sources

Building the Native CLI

The hardwood CLI compiles to a GraalVM native binary. Requires GraalVM (Java 25+) installed locally — install via SDKMAN:

sdk install java 25.0.2-graalce

Then build the cli module and its dependencies:

./mvnw -Dnative package -pl cli -am

The resulting binary is at cli/target/hardwood-cli.

See NATIVE_BUILD.md for the full build guide — obtaining a Linux binary, the Docker image, and how the native build works (codec handling, build arguments).

Building the Documentation

The documentation site can be previewed locally using Docker:

# Build the image (once, or after changing requirements.txt)
docker build -t hardwood-docs docs/

# Serve locally with hot reload — preview at http://127.0.0.1:8000
docker run --rm -p 8000:8000 -v "$(pwd):/repo" hardwood-docs

# Build static site (output in docs/site/)
docker run --rm -v "$(pwd):/repo" hardwood-docs build -f docs/mkdocs.yml

The serve command polls the mounted repository, so edits to docs/content, docs/overrides, docs/hooks, and docs/mkdocs.yml rebuild the site and refresh the open browser tab. Changes to docs/requirements.txt require a rebuild of the image.

Running Claude Code

A Docker Compose set-up is provided for running Claude Code with all build dependencies (Java 25, Maven, gh) pre-installed.

GH_TOKEN=<your-token> docker compose run --rm claude

Set GH_TOKEN to a GitHub personal access token so that Claude Code can interact with issues and pull requests. The project directory is mounted into the container at /workspace, and Claude Code configuration is persisted in a named volume across sessions.

Creating a Release

See RELEASING.md.

API Change Report

To generate an API change report across all published modules (hardwood-core, hardwood-avro, hardwood-s3, hardwood-aws-auth):

tools/api-report.sh <PREVIOUS_VERSION>                  # HEAD (snapshot) vs <PREVIOUS_VERSION>
tools/api-report.sh <PREVIOUS_VERSION> <LATER_VERSION>  # compare two published versions

In the default (single-argument) form the script installs the snapshot jars and compares HEAD against <PREVIOUS_VERSION>. With both arguments the build step is skipped and both sides are resolved from the Maven repository — useful to diff arbitrary released pairs (e.g. Beta2 against CR1).

The script writes a unified target/japicmp/api-report.diff (text) and target/japicmp/api-report.html (one document with a TOC linking to per-module sections). Per-module reports also land under target/japicmp/<artifactId>/ (HTML / Markdown / XML / diff). Internal packages (dev.hardwood.internal) are excluded. This is run automatically during releases.

Performance

See PERFORMANCE.md for benchmark results and instructions on running performance tests.


License

This code base is available under the Apache License, version 2.


Resources

Contributors

(top 30 of 39)

gunnarmorling

634 commits

rionmonster

66 commits

iifawzi

53 commits

hardwood-hq/hardwood

A fast minimal dependency implementation of Apache Parquet

371

stars

852

commits

Java

primary language

Sep 11, 2026

updated

hardwood.dev/
apache-parquet
columnar-format
hardwood
parquet
parquet-tools
performance

README

Hardwood logo

Hardwood

A reader and writer for the Apache Parquet file format, optimized for minimal dependencies and great performance. Available as a Java library and a command-line tool.

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 — see the read benchmarks
  • 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: Supports all Parquet files which the canonical parquet-java library supports, and a drop-in shim module eases migration from it, with documented divergences where Hardwood applies stricter semantics (e.g. SQL three-valued notEq)

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.

Latest version: 1.0.0.Final, 2026-06-25

Documentation

Full documentation is available at hardwood.dev.

Quick Start

<dependency>
    <groupId>dev.hardwood</groupId>
    <artifactId>hardwood-core</artifactId>
    <version>1.0.0.Final</version>
</dependency>

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()));
    }
}

See the Getting Started guide for detailed setup instructions.

Limitations

  • Local files (memory-mapped via InputFile.of(Path)) may be arbitrarily large; each individual column chunk must be at most 2 GB of compressed data.
  • In-memory (InputFile.of(ByteBuffer)) and object-store sources are limited to 2 GB per file. Split larger datasets across multiple files and read them with Hardwood.openAll(...) or ParquetFileReader.openAll(...).
  • Writing is under active development as of Hardwood 1.1, and is documented in the development docs until 1.1 is released. It targets local files via OutputFile.of(Path); output to object storage is coming soon.

Repository Documentation

DocumentPurpose
ARCHITECTURE.mdHigh-level architecture and module layout.
CONTRIBUTING.mdHow to contribute: workflow, commit format, PR expectations.
ROADMAP.mdImplementation status, roadmap, and milestones.
NATIVE_BUILD.mdHow the GraalVM native CLI build works.
PERFORMANCE.mdBenchmark results and how to run performance tests.
TESTING.mdManual testing recipes (e.g. S3 via s3proxy).
RELEASING.mdRelease process.

Articles, Talks & Podcasts

See hardwood.dev for a list of articles, talks, and podcasts about Hardwood.


Contributing

Contributions are welcome! See CONTRIBUTING.md for the full guide — how to find work, the issue-first workflow, commit message format, and PR expectations.

LLM-assisted contributions are welcome, but vibe coding — accepting AI-generated changes without understanding them — is not. The aspiration is a high-quality, maintainable, performant, safe codebase.

See ROADMAP.md for the detailed implementation status, roadmap, and milestones.


Build

This project requires Java 25 or newer for building (to create the multi-release JAR with Java 22+ FFM support). The resulting JAR runs on Java 21+ (libdeflate support requires Java 22+).

Docker must be running for the build to succeed, as the test suite uses Testcontainers to spin up services (e.g. S3 integration tests). If Docker is not available, the build will fail during the test phase for these tests.

It comes with the Apache Maven wrapper, i.e. a Maven distribution will be downloaded automatically, if needed.

Run the following command to build this project:

./mvnw clean verify

On Windows, run the following command:

mvnw.cmd clean verify

Pass the -Dquick option to skip all non-essential plug-ins and create the output artifact as quickly as possible:

./mvnw clean verify -Dquick

Run the following command to format the source code and organize the imports as per the project's conventions:

./mvnw process-sources

Building the Native CLI

The hardwood CLI compiles to a GraalVM native binary. Requires GraalVM (Java 25+) installed locally — install via SDKMAN:

sdk install java 25.0.2-graalce

Then build the cli module and its dependencies:

./mvnw -Dnative package -pl cli -am

The resulting binary is at cli/target/hardwood-cli.

See NATIVE_BUILD.md for the full build guide — obtaining a Linux binary, the Docker image, and how the native build works (codec handling, build arguments).

Building the Documentation

The documentation site can be previewed locally using Docker:

# Build the image (once, or after changing requirements.txt)
docker build -t hardwood-docs docs/

# Serve locally with hot reload — preview at http://127.0.0.1:8000
docker run --rm -p 8000:8000 -v "$(pwd):/repo" hardwood-docs

# Build static site (output in docs/site/)
docker run --rm -v "$(pwd):/repo" hardwood-docs build -f docs/mkdocs.yml

The serve command polls the mounted repository, so edits to docs/content, docs/overrides, docs/hooks, and docs/mkdocs.yml rebuild the site and refresh the open browser tab. Changes to docs/requirements.txt require a rebuild of the image.

Running Claude Code

A Docker Compose set-up is provided for running Claude Code with all build dependencies (Java 25, Maven, gh) pre-installed.

GH_TOKEN=<your-token> docker compose run --rm claude

Set GH_TOKEN to a GitHub personal access token so that Claude Code can interact with issues and pull requests. The project directory is mounted into the container at /workspace, and Claude Code configuration is persisted in a named volume across sessions.

Creating a Release

See RELEASING.md.

API Change Report

To generate an API change report across all published modules (hardwood-core, hardwood-avro, hardwood-s3, hardwood-aws-auth):

tools/api-report.sh <PREVIOUS_VERSION>                  # HEAD (snapshot) vs <PREVIOUS_VERSION>
tools/api-report.sh <PREVIOUS_VERSION> <LATER_VERSION>  # compare two published versions

In the default (single-argument) form the script installs the snapshot jars and compares HEAD against <PREVIOUS_VERSION>. With both arguments the build step is skipped and both sides are resolved from the Maven repository — useful to diff arbitrary released pairs (e.g. Beta2 against CR1).

The script writes a unified target/japicmp/api-report.diff (text) and target/japicmp/api-report.html (one document with a TOC linking to per-module sections). Per-module reports also land under target/japicmp/<artifactId>/ (HTML / Markdown / XML / diff). Internal packages (dev.hardwood.internal) are excluded. This is run automatically during releases.

Performance

See PERFORMANCE.md for benchmark results and instructions on running performance tests.


License

This code base is available under the Apache License, version 2.


Resources

Contributors

(top 30 of 39)

gunnarmorling

634 commits

rionmonster

66 commits

iifawzi

53 commits

Languages

Java

94.7%

Python

4.4%