Native Java port of protowire — a protobuf-backed
serialization toolkit. Implements the canonical wire format defined in
trendvidia/protowire and
verified for byte-equivalence against eight sibling ports (Go, C++, Rust,
TypeScript, Python, C#, Swift, Dart).
Java 21, Gradle multi-module, protobuf-gradle-plugin, JUnit 5.
Maven:
<dependency>
<groupId>org.protowire</groupId>
<artifactId>protowire-pxf</artifactId>
<version>0.70.0</version>
</dependency>Gradle (Kotlin DSL):
implementation("org.protowire:protowire-pxf:0.70.0")
// or pick the modules you need:
// protowire-pb, protowire-pxf, protowire-sbe, protowire-envelope, protowire-proto-annotationsAll published artifacts share the 0.70.x line; ports at the same minor
implement the same wire contract.
| Module | Java package | Notes |
|---|---|---|
:pb |
org.protowire.pb |
Schema-free struct ↔ proto3 binary marshaling. Field numbers come from the @ProtoField(N) annotation — the Java analogue of Go's protowire:"N" struct tag. |
:pxf |
org.protowire.pxf |
PXF text ↔ Message / DynamicMessage. Two-tier decoder split: Pxf.parse() returns an AST with comments; Pxf.unmarshal / Pxf.unmarshalFull use a fused fast decoder. |
:sbe |
org.protowire.sbe |
FIX SBE binary codec, driven by SBE annotations on .proto schemas. Codec.marshal, Codec.unmarshal, and a zero-allocation View. Includes Convert.xmlToProto / Convert.protoToXml. |
:envelope |
org.protowire.envelope |
Standard API response envelope, generated from envelope/v1/envelope.proto, with Envelopes builders + queries. |
:proto-annotations |
org.protowire.proto.{pxf,sbe} |
Compiled proto annotations: pxf.required, pxf.default, pxf.BigInt/Decimal/BigFloat, sbe.schema_id/template_id/length/encoding. |
:dump-envelope, :bench-pxf, :bench-sbe |
(test harnesses) | Per-port binaries used by the cross-port runner scripts in the spec repo. Not part of the public library API. |
./gradlew build # compile + test all modules
./gradlew :pxf:test # only PXFJava 21 toolchain is required (configured automatically via Gradle).
import org.protowire.pxf.Pxf;
import org.protowire.pxf.Result;
// schema-bound (compiled-in proto)
ServerConfig.Builder b = ServerConfig.newBuilder();
Pxf.unmarshal(pxfBytes, b);
// dynamic
DynamicMessage msg = Pxf.unmarshal(pxfBytes, descriptor);
// presence tracking
Result r = Pxf.unmarshalFull(pxfBytes, b);
r.isSet("name");
r.isNull("email");
r.isAbsent("role");byte[] out = Pxf.marshal(msg);
// with options
byte[] out = MarshalOptions.defaults()
.withTypeUrl("infra.v1.ServerConfig")
.withEmitDefaults(true)
.marshal(msg);Ast.Document doc = Pxf.parse(pxfBytes);
byte[] formatted = Pxf.formatDocument(doc);The AST is a sealed hierarchy of records (Ast.Document, Ast.Entry, Ast.Value); pattern matching is used throughout the formatter and decoder.
Mirrors the Go module and the C++ port:
- AST path —
Parser.parse()produces anAst.Documentwith comments attached. Used when comment preservation is needed. - Fast path —
FastDecoderfuses lexing + decoding in a single pass, writing directly into aMessage.Builderwith no intermediate AST allocation. Used byPxf.unmarshalandPxf.unmarshalFull.
PXF (:pxf):
- ✅ Lexer: single/triple strings, base64 bytes, RFC 3339 timestamps, Go-style durations, comments, dedent.
- ✅ Schema-bound encoder + decoder via Java protobuf reflection (descriptors /
DynamicMessage). - ✅ Scalars, enums, repeated, maps, nested messages, oneof.
- ✅ Well-known types:
Timestamp,Duration, all wrapper types (sugar form). - ✅ Field-presence tracking (
unmarshalFullreturns aResult). - ✅
google.protobuf.Anysugar (block syntax with@type =). - ✅
pxf.BigInt/pxf.Decimal/pxf.BigFloatsugar inside PXF text. - ✅
_nullFieldMaskdiscovery and emission across binary round-trips. - ✅
(pxf.required)/(pxf.default)annotation enforcement inunmarshalFull. - ✅ AST-preserving
formatDocument.
SBE (:sbe):
- ✅ Codec construction from
FileDescriptors, schema/version/template-id discovery via SBE annotations. - ✅
marshal/unmarshalfor proto messages, including composites and repeating groups. - ✅ Type-narrowing via
(sbe.encoding)overrides. - ✅ Zero-allocation
View/GroupView. - ✅ XML schema parsing and
Convert.xmlToProto/Convert.protoToXml.
pb (:pb):
- ✅ Wire format for all proto3 scalar types, repeated, embedded messages.
- ✅
BigInteger/BigDecimalbyte-backed types matchingpxf.BigInt/Decimalschemas. - ✅ Unknown-field skipping on decode.
envelope (:envelope):
- ✅ Generated
Envelope/AppError/FieldErrorfrom proto plusEnvelopesbuilders + queries.
Every published jar carries
reachability metadata
under META-INF/native-image/org.protowire/<artifact>/, so the
native-image agent doesn't warn about missing hints when consumers
build a native binary.
:pb consumers using the schema-free @ProtoField-driven marshaler
must register their own DTO classes for reflection — see
pb/src/main/resources/META-INF/native-image/org.protowire/protowire-pb/native-image.properties
for the rationale and template.
The protowire CLI is shared across every port and lives in the spec repo at github.com/trendvidia/protowire/cmd/protowire. Install:
go install github.com/trendvidia/protowire/cmd/protowire@latestJava users use this library for in-process encode/decode and the shared CLI for command-line operations. There is no separate Java CLI binary.
Verified by the exampleFixtureRoundTrip test in pxf/src/test/java/.../PxfTest.java: the same testdata/example.pxf from the Go module is parsed, marshaled, and re-parsed; the two DynamicMessage instances must compare equal.
For Kotlin, Scala, Clojure, and any other JVM language, the native Java JAR is consumed directly. A wrapper around a non-JVM implementation would not be.