FastProto lets you map binary protocols to plain Java classes using annotations, then decode/encode with one-liners. This guide shows the essentials.
Add the dependency via Maven:
<dependency>
<groupId>org.indunet</groupId>
<artifactId>fastproto</artifactId>
<version>4.1.0</version>
</dependency>Suppose you receive a 20-byte packet from a weather device.
Without FastProto you typically hand‑code byte offsets and bit operations:
byte[] datagram = ...;
int id = datagram[0] & 0xFF;
long timeMillis =
((long) datagram[2] & 0xFF) |
(((long) datagram[3] & 0xFF) << 8) |
(((long) datagram[4] & 0xFF) << 16) |
(((long) datagram[5] & 0xFF) << 24) |
(((long) datagram[6] & 0xFF) << 32) |
(((long) datagram[7] & 0xFF) << 40) |
(((long) datagram[8] & 0xFF) << 48) |
(((long) datagram[9] & 0xFF) << 56);
Timestamp time = new Timestamp(timeMillis);
int humidity = (datagram[10] & 0xFF) | ((datagram[11] & 0xFF) << 8);
int temperature = (short) ((datagram[12] & 0xFF) | ((datagram[13] & 0xFF) << 8));
long rawPressure =
((long) datagram[14] & 0xFF) |
(((long) datagram[15] & 0xFF) << 8) |
(((long) datagram[16] & 0xFF) << 16) |
(((long) datagram[17] & 0xFF) << 24);
double pressure = rawPressure * 0.1;
boolean deviceValid = (datagram[18] & 0x01) != 0;As protocols evolve and fields increase, this style of manual bit‑twiddling becomes verbose and fragile—offsets, endianness and signedness are easy to get wrong.
With FastProto you instead annotate a Java class with offsets to map fields to bytes/bits:
import java.sql.Timestamp;
import org.indunet.fastproto.annotation.UInt8Type;
import org.indunet.fastproto.annotation.BoolType;
import org.indunet.fastproto.annotation.TimeType;
import org.indunet.fastproto.annotation.UInt16Type;
import org.indunet.fastproto.annotation.Int16Type;
import org.indunet.fastproto.annotation.UInt32Type;
public class Weather {
@UInt8Type(offset = 0)
int id;
@TimeType(offset = 2)
Timestamp time;
@UInt16Type(offset = 10)
int humidity; // %RH
@Int16Type(offset = 12)
int temperature; // ℃
@UInt32Type(offset = 14)
long pressureRaw; // Pa (raw)
@BoolType(byteOffset = 18, bitOffset = 0)
boolean deviceValid;
}import org.indunet.fastproto.FastProto;
byte[] datagram = /* 20 bytes from device */ new byte[20];
// Decode bytes into a Weather object
Weather weather = FastProto.decode(datagram, Weather.class);
// Encode object back to bytes (explicit length optional)
byte[] out = FastProto.encode(weather, 20);Use formulas to transform values on the fly (e.g., convert raw pressure to engineering units).
import org.indunet.fastproto.annotation.DecodingFormula;
import org.indunet.fastproto.annotation.EncodingFormula;
import org.indunet.fastproto.annotation.UInt32Type;
public class WeatherWithPressure {
@UInt32Type(offset = 14)
@DecodingFormula(lambda = "x -> x * 0.1") // raw -> Pa*0.1
@EncodingFormula(lambda = "x -> (long) (x * 10)") // Pa -> raw
double pressure; // Pa
}For complex logic, implement java.util.function.Function and reference the class. See docs/formulas.md.
Set defaults at the class level and override per field as needed.
import org.indunet.fastproto.BitOrder;
import org.indunet.fastproto.ByteOrder;
import org.indunet.fastproto.annotation.DefaultBitOrder;
import org.indunet.fastproto.annotation.DefaultByteOrder;
import org.indunet.fastproto.annotation.UInt16Type;
import org.indunet.fastproto.annotation.BoolType;
@DefaultByteOrder(ByteOrder.BIG)
@DefaultBitOrder(BitOrder.LSB_0)
public class WeatherOrderDemo {
@UInt16Type(offset = 10, byteOrder = ByteOrder.LITTLE)
int humidity;
@BoolType(byteOffset = 18, bitOffset = 0, bitOrder = BitOrder.MSB_0)
boolean deviceValid;
}Details: docs/byte-and-bit-order.md.
Compute and store checksums automatically during encode, and validate on decode.
import org.indunet.fastproto.ByteOrder;
import org.indunet.fastproto.annotation.Checksum;
import org.indunet.fastproto.annotation.UInt8Type;
public class Packet {
@UInt8Type(offset = 0) int b1;
@UInt8Type(offset = 1) int b2;
@UInt8Type(offset = 2) int b3;
@UInt8Type(offset = 3) int b4;
@UInt8Type(offset = 4) int b5;
// Compute CRC16 over [0,5) and write LE result at offsets 5..6
@Checksum(start = 0, length = 5, offset = 5, type = Checksum.Type.CRC16, byteOrder = ByteOrder.LITTLE)
int crc16;
}Utilities are also available: docs/checksum.md.
Decode directly into a POJO by field names:
import org.indunet.fastproto.FastProto;
class DataObject { Boolean f1; Integer f2; Integer f3; }
byte[] bytes = /* incoming data */ new byte[16];
DataObject obj = FastProto.decode(bytes)
.readBool("f1", 0, 0)
.readInt8("f2", 1)
.readInt16("f3", 2)
.mapTo(DataObject.class);Create binary blocks imperatively:
import org.indunet.fastproto.ByteOrder;
import org.indunet.fastproto.FastProto;
byte[] out = FastProto.create(16)
.writeInt8(0, 1)
.writeUInt16(2, 3, 4)
.writeUInt32(6, ByteOrder.BIG, 256)
.get();Or via utility methods:
import org.indunet.fastproto.ByteOrder;
import org.indunet.fastproto.util.EncodeUtils;
byte[] buf = new byte[16];
EncodeUtils.writeInt8(buf, 0, 1);
EncodeUtils.writeUInt16(buf, 2, 3, 4);
EncodeUtils.writeUInt32(buf, 6, ByteOrder.BIG, 256);More: docs/without-annotations.md. For Android notes, see docs/android.md.