Release Notes¶
See GitHub Releases for downloads and more information.
1.1.0-SNAPSHOT¶
-
isNullandisNotNullaccept the name of a group — a struct, aLISTor aMAP— testing whether the group itself is present rather than one of its fields (#977). -
FilterPredicate.in(String, double...)filtersFLOATandDOUBLEcolumns by set membership (#868). -
Statistics whose
minsorts above itsmaxno longer prune (#1172). -
Statistics carrying a null count and no bounds are no longer reported as carrying deprecated
min/maxbounds (#1172). -
A
ParquetFileReaderno longer retains a read'sRowGroupIteratorafter the reader consuming it is closed (#1170). -
A logical type annotation that a column's physical type cannot carry is now dropped, and the column is read as its physical type. Previously it surfaced as an
IllegalArgumentExceptionfrom whichever accessor first reached the column (#1139).FLOAT16is defined as a two-byte payload, so a column annotatedFLOAT16that declares three bytes is invalid; parquet-format PR 606 specifies that readers ignore the annotation and use only the physical type. An annotation this version does not recognize is dropped the same way, so a file written against a newer format version can still be read. Both cases log a warning, naming the column and the reason, or the union field that was not recognized. What changes for you:getFileSchema()reports no logical type for such a column,getValuereturns its physical value where it used to throw, and a logical accessor on it fails as it does on any unannotated column. -
A multi-file read opens each file as it reaches it, rather than opening every file when the reader is built, so the time to the first row no longer grows with the number of files (#1107).
- A later file's I/O errors, and any
SchemaIncompatibleExceptionits schema raises, now surface from the reading loop rather than fromParquetFileReader.openAll(...)orbuild()— always before any row of that file is returned. Code that catches those around reader construction alone should catch them around iteration too.
- A later file's I/O errors, and any
-
Every
LogicalTypemember has a static factory, and those are the documented way to construct one —LogicalType.string(),LogicalType.decimal(18, 2),LogicalType.timestamp(true, TimeUnit.MICROS)(#1074). The parameterless ones return a shared instance, which the reader now hands back instead of allocating a record per column while it decodes a footer. The record constructors still work. -
A
LocalDatepredicate requires the column to carry theDATEannotation, as its JavaDoc has always said (#1141). The annotation went unchecked before, so aLocalDateagainst a plainINT32column compared epoch days against unrelated integers and returned rows answering a different question, with nothing raised. What changes for you: such a call now throwsIllegalArgumentExceptionat reader creation. A plainINT32column that does hold epoch days is filtered by the day itself —gt("d", (int) date.toEpochDay()). -
A
Stringpredicate on aDECIMALorFLOAT16column compares as the column does — aDECIMALby its unscaled value, aFLOAT16by the number its two bytes encode — rather than as a byte string (#1142). Both order by the value their bytes stand for and record their statistics that way, so comparing byte-wise pruned row groups against bounds written in a different order and silently dropped matching rows. This is the comparison parquet-java applies, so a filter carried over through the compatibility shim answers the same.inStringscompares its probes the same way — which is also how parquet-java evaluatesIn— so a padded encoding of aDECIMALprobe is found; on aFLOAT16column each probe is compared as the half it encodes, andin(double...)accepts aFLOAT16column too. -
An ordered predicate on an
INT(bitWidth, isSigned = false)column compares by unsigned magnitude, the order the column is written in (#1144). The comparison was signed before, solt,gtand their siblings returned wrong rows on a column holding values above 2^31, and bounds straddling that point read as inverted and were discarded — costing those columns row-group and page skipping as well.
Breaking Changes:
-
The reader's exception model separates what the transport got wrong from what the file did, so a failure says whether trying again can help (#1104).
IOExceptionnow means the transport — a read that failed, a connection reset — and is declared where the reader reaches the file:RowReader.hasNext/next/closeandColumnReader.nextBatch/close, asParquetFileWriterhas always declared it. The canonical idiom is unaffected, becauseParquetFileReader.open(...)already declaredIOExceptionand so the enclosing method already handles it:
try (ParquetFileReader reader = ParquetFileReader.open(file); RowReader rows = reader.buildRowReader().build()) { while (rows.hasNext()) { rows.next(); } }What does need a change is a method that receives an already-open reader and reads from it without otherwise touching
IOException— a helper likevoid print(RowReader rows)— and any read inside a lambda whose functional interface forbids a checked exception, such asforEachorStream.map. - A corrupt file now raises the new uncheckedParquetReadExceptionrather thanIOException: bad magic, a corrupt footer, a malformed page index, a misplaced dictionary page, a failed checksum, values that will not decode.SchemaIncompatibleExceptionextends it. This one is silent — acatch (IOException)written for corruption keeps compiling and stops catching. It is released alongside thethrowsclauses deliberately, so the compile error brings you to the error handling the silent change would otherwise slip past. - A row group whose page-index region exceeds 2 GB now raisesUnsupportedOperationExceptionwhere it used to raiseIOException. Silent, like the one above — acatch (IOException)written for it keeps compiling and stops catching. -RowReaderandColumnReaderimplementCloseable, asParquetFileWriterandInputFilealready did. - A page that will not decompress and a dictionary that will not decode now raiseParquetReadExceptiontoo. Decompressing and parsing work on bytes already in memory, so they cannot fail at I/O and no longer say they can. Silent, like the two above. - A codec library that is absent, or a native one that will not load, now reaches you as theUnsupportedOperationExceptionit always was on other paths. On the dictionary path it was being caught and reported as a failed read, which buried the message naming the dependency to add. - A column chunk stored in a separate file (the legacy split-file layout) and a file over 2 GB opened with the mmap-backed range cache now raiseUnsupportedOperationExceptionrather thanIOException. Both files are correct; it is Hardwood that will not read them. Silent. - The writer raises the new uncheckedParquetWriteExceptionwhen a compression codec rejects a page body, where it used to raiseIOException. Nothing you passed was wrong and the destination is not involved, so retrying cannot help. Silent. - A corrupt value inside the metadata — a malformed bloom filter header, a geospatial bounding box missing a required field, a decimal scale or precision that cannot be, an unknown physical type, repetition type, codec or time unit — now raisesParquetReadExceptionrather thanIllegalArgumentExceptionorIllegalStateException. These are the file being wrong, not your call being wrong. Silent. Both types keep their meaning for calls that really are mistakes, such as asking for a column outside the projection. -
LogicalType.DecimalTypetakes its precision before its scale, where it used to take scale first (#1074). Every other decimal API takes them in that order — SQL'sDECIMAL(p, s), Arrow, Avro, Iceberg, parquet-cpp — and it is the order the annotation renders in. Onlyparquet.thrift's field declaration and the APIs that mirror it read the other way. This one is silent, because a swapped call still compiles:LogicalType.decimal(...)rejects a scale above the precision, which catches a transposed pair at the call site, but a column whose scale equals its precision passes either way.
1.1.0.Beta1 (2026-08-31)¶
Announcement blog post · API changes
Highlights of this release:
- Parquet files can be written —
RowWritera record at a time,ColumnWriterin aligned batches of primitive arrays — over the full type system, nested structs, lists and maps included- Every encoding and compression codec the format defines, bar the deprecated
LZ4framing andLZO - Statistics and
nan_countare written, page indexes andSizeStatisticsare not - Footer key-value metadata and the
created_byidentifier are set onParquetFileWriter, at any point untilclose()writes the footer
- Every encoding and compression codec the format defines, bar the deprecated
- Two push-down sources for skipping non-matching row groups
- A column's Bloom filter, for
eqandinpredicates - The chunk's dictionary page, when its encoding stats show every data page is dictionary-encoded
- A column's Bloom filter, for
- Further improved read performance
- A fast path for clean fixed-length
LISTpages - Bulk-unpacked
DELTA_BINARY_PACKEDminiblocks - Dictionary indices of 9 to 32 bits read a word at a time
- All-present definition levels are no longer materialized
- A fast path for clean fixed-length
- Multi-file reading
- Columns are matched by field path, so files may declare them in any order; a real mismatch raises
SchemaIncompatibleExceptionas the file opens - Physical
skip(N)is a true global offset across the concatenated files - Per-file metadata through
ParquetFileReader.getFileCount()andgetFileMetaData(int)
- Columns are matched by field path, so files may declare them in any order; a real mismatch raises
- Correctness fixes
- A
FilterPredicatenaming a group resolved to one of its leaves and returned wrong rows; a group name is now rejected withIllegalArgumentExceptionat reader creation - Batch sizing accounts for list fan-out, so a high-fan-out repeated column no longer drains a whole file into a single batch and exhausts the heap
GeographyType.algorithmis read as the enum it is, so a non-spherical geography column no longer reads asSPHERICAL; an unrecognized algorithm reportsEdgeInterpolationAlgorithm.UNKNOWN- Variant decoding bounds-checks lengths, guards 32-bit offset arithmetic, corrects the metadata offset-size read, and limits nesting depth
- A
- The
hardwoodCLI moves from picocli/Quarkus to aesh, which makes it start fasterdivenavigation is unified across all screens: every key moves the cursor,▶marks whatEnteracts onconvertpreserves types and NULL — JSON writes real scalars andnull, CSV an empty field, with--null-stringto overrideinfosurfaces the file's key-value metadata
Breaking Changes:
Validitymoves todev.hardwood, andColumnReader's rawgetDefinitionLevels()/getRepetitionLevels()give way to the layer model (#9, #749)ColumnIndex.nullPages()andnullCounts()returnboolean[]andlong[], and the canonical constructors ofColumnChunk,ColumnIndex,ColumnMetaDataandOffsetIndextake further components — reading these records is unaffected, constructing them directly is not (#607, #856, #903)S3InputFile.length()declaresthrows IOException, matching theInputFilecontract (#1072)hardwood help <command>is removed — usehardwood <command> --help(#686)
See the 1.1.0.Beta1 milestone on GitHub for the full list of resolved issues.
Thank you to all contributors to this release: Arnab Nandy, Chandan Dhamande, Fawzi Essam, Florian Meyer, Gunnar Morling, Hursh, Hyungun, Joshua Buss, Karen Barseghyan, Kohinoor Gupta, Mehmet Turac, Mingjie Zhao, Morax, Nikulin Nikita, Rion Williams, Sebastian Legarraga, Semyon Sinchenko, Shaik Sameer, Shril Kumar, Ståle Pedersen.
1.0.0.Final (2026-06-25)¶
Announcement blog post · API changes
Highlights of this release:
- Float and double row group and page pruning honors the file's column order, with
ColumnOrdersurfaced on the API;ResolvedPredicatefloat/double convenience constructors are now public - Legacy list encodings from older writers — un-annotated repeated fields and 2-level lists — are recognized as lists
- MAP columns without a value field (key-only
key_valuegroups) are read instead of throwing - Sub-field projections into MAP values and VARIANT groups pull in the structural columns they require (the MAP's key, every VARIANT leaf)
AvroRowReaderhonors column projections, and DECIMAL, UUID, UINT_32, and FIXED columns now read correctly- The multi-file
Hardwoodentry point accepts a caller-suppliedHardwoodContext, for control over decoder thread-pool sizing and sharing a context across readers ColumnReaderandRowReaderclose()methods are now idempotent, fixing a close-time performance regression from Beta2- Logical types render as Parquet-style annotation tokens (e.g.
STRING) hardwood divefails fast with a clear message when stdout is not an interactive terminalhardwood printshort option names follow the conventional single-dash, single-character form (-s,-w,-i,-d);--transposeis long-only
See the 1.0.0.Final milestone on GitHub for the full list of resolved issues.
Thank you to all contributors to this release: Fawzi Essam, Gunnar Morling, Leo Chashnikov, Mohamed Ibrahim Elsawy, Rion Williams, Yash Priyadarshan.
1.0.0.CR2 (2026-06-07)¶
Highlights of this release:
- Breaking:
RowReaderBuilder.firstRow()renamed toskip(), which now composes with a filter as a logicalOFFSET— rows are skipped after the filter is applied - Docker distribution of the
hardwoodCLI, published as a multi-arch image to GHCR - Configurable read batch size for
ColumnReader/ColumnReaders, with the default now sized adaptively from the projected column widths instead of a fixed record count - TIMESTAMP accessors honor
isAdjustedToUTC, with dedicated accessors for local (non-UTC) timestamps - Stricter metadata validation: negative sizes, counts, and offsets are rejected, as are shredded Variant objects repeating a field across
typed_valueandvalue - NaN-safe row group and page pruning for
floatanddoublecolumns - Duplicate map keys resolve to the last value per the Parquet spec
head()with a filter caps matched rows rather than scanned rows- Legacy MAP columns from older parquet-mr / Hive / Impala writers (which annotate only the inner
key_valuegroup) are now recognized as maps - API change reports are now published alongside the JavaDoc on the website
See the 1.0.0.CR2 milestone on GitHub for the full list of resolved issues.
Thank you to all contributors to this release: Alexei Zenin, Fawzi Essam, Gunnar Morling, Mohamed Ibrahim Elsawy.
1.0.0.CR1 (2026-05-31)¶
Announcement blog post · API changes
Highlights of this release:
- Breaking:
ColumnReaderrebuilt around a layer model, with per-layer validity, offsets, and real-item-only sizing for nested data (see the Layer Model docs);ColumnReaderis now marked@Experimental - More performant evaluation of multi-column filter expressions
- Split-aware reading via
RowGroupPredicate.byteRange(...), for Hadoop-style split integrations - Coordinated multi-column reads via
ColumnReaders.nextBatch()/getRecordCount() - Richer
RowReadervalue model: by-index field access onPqStruct, key-based lookup and typed accessors onPqMap, typedListaccessors onPqList, and additional variant accessors - Float16 logical type support (readable values and filter predicates) and recognition of the
NullTypelogical annotation - First-cut geospatial support (GEOMETRY/GEOGRAPHY logical types and bounding-box metadata)
- Reading of local files larger than 2 GB
- CLI: exhaustive logical-type formatting;
hardwood dive: faster navigation of large collections and corrected "go to latest" in the data preview
See the 1.0.0.CR1 milestone on GitHub for the full list of resolved issues.
Thank you to all contributors to this release: Carlos Sousa, Fawzi Essam, Gunnar Morling, Manish, Mohamed Ibrahim Elsawy, muhannd Sayed, polo, Prashant Khanal, Rion Williams, Said Boudjelda.
1.0.0.Beta2 (2026-04-29)¶
Announcement blog post · API changes
Highlights of this release:
- Interactive
hardwood diveTUI for exploring Parquet files - Parquet Variant logical type, including shredded reassembly
- Additional logical types: INTERVAL, MAP/LIST, INT96 timestamps
- Faster reads via a parallel per-column pipeline and per-column in-page row skipping
- Reduced S3 traffic via byte-range caching, coalesced GETs, and small-column fetches
- Unified reader API based on builders
- CLI with reorganized
inspectsubcommands
See the 1.0.0.Beta2 milestone on GitHub for the full list of resolved issues.
Thank you to all contributors to this release: André Rouél, Brandon Brown, Bruno Borges, Fawzi Essam, Gunnar Morling, Manish, polo, Rion Williams, Sabarish Rajamohan, Trevin Chow.
1.0.0.Beta1 (2026-04-02)¶
Announcement blog post · API changes
Highlights of this release:
- S3 and remote object store support with coalesced reads
- CLI tool for inspecting and querying Parquet files
- Avro
GenericRecordsupport via thehardwood-avromodule - Row group filtering with predicate push-down and page-level column index filtering
InputFileabstraction for pluggable file sources- S3 support and filtering in the parquet-java compatibility layer
- Project documentation site
See the 1.0.0.Beta1 milestone on GitHub for the full list of resolved issues.
Thank you to all contributors to this release: Arnav Balyan, Brandon Brown, Gunnar Morling, Manish, Nicolas Grondin, Rion Williams, Romain Manni-Bucau, Said Boudjelda.
1.0.0.Alpha1 (2026-02-26)¶
Highlights of this release:
- Zero-dependency Parquet file reader for Java
- Row-oriented and columnar read APIs
- Support for flat and nested schemas (lists, maps, structs)
- All standard encodings (RLE, DELTA_BINARY_PACKED, DELTA_BYTE_ARRAY, BYTE_STREAM_SPLIT, etc.)
- Compression: Snappy, ZSTD, LZ4, GZIP, Brotli
- Projection push-down, parallel page pre-fetching, and memory-mapped file I/O
- Multi-file reader and
parquet-javacompatibility layer - Optional Vector API acceleration on Java 22+
- JFR events for observability
- BOM for dependency management
Thank you to all contributors to this release: Andres Almiray, Gunnar Morling, Rion Williams.