Protobuf Schemas
Protocol Buffers (Protobuf) is a language- and platform-neutral mechanism for serialising structured data.
In MAPS, Protobuf is used wherever you want:
- strong typing
- explicit schema evolution
- compact binary representation
- multi-language interoperability
MAPS integrates Protobuf schemas through SchemaConfig with format: "protobuf" and a Protobuf-specific configuration object stored in schema.
1. Description
Protobuf schemas are defined in .proto files, compiled to language-specific code and a binary descriptor set.
MAPS uses a small JSON wrapper plus the compiled descriptors to:
- identify the root message type
- resolve nested message types
- decode/encode payloads to/from Typed Events
Once decoded, Protobuf behaves like any other schema type in MAPS: all downstream processing sees a Typed Event, not raw Protobuf messages.
2. Protobuf Schema Definition
2.1 Example .proto definition
syntax = "proto3";
package maps.sensors;
message Bme688Reading {
string device_id = 1;
int64 timestamp = 2; // epoch millis
double temperature = 3; // °C
double humidity = 4; // %RH
double pressure = 5; // hPa
double gas = 6; // Ω
string heater_mode = 7;
string gas_mode = 8;
}
This is the canonical Protobuf schema.
MAPS does not rewrite this; it consumes the compiled descriptors.
3. Protobuf SchemaConfig in MAPS
Protobuf schemas in MAPS are stored as a SchemaConfig with:
format="protobuf"schema= a Protobuf-specific config object
The Protobuf config is represented as:
public static final class ProtobufConfig {
private String messageName;
private byte[] descriptorValue;
}
In the SchemaConfig, this is serialised into JSON as:
"schema": {
"messageName": "fully.qualified.MessageName",
"descriptorValue": "<base64-encoded descriptor bytes>"
}
Important:
Protobuf uses only theschemaobject.
For Protobuf,schemaBase64is not used and must remain unset.
MAPS expects descriptorValue to contain a base64-encoded descriptor payload, typically:
- a
FileDescriptorSet, or - a descriptor for the root message and its dependencies.
4. Example SchemaConfig
{
"versionId": "1",
"name": "Bme688-Protobuf",
"description": "BME688 sensor reading using Protobuf encoding.",
"labels": {
"uniqueId": "e63f4f0f-1ef9-4b6a-8c7a-7c7b27e9f111",
"resource": "sensor",
"interface": "sensor.bme688",
"comments": "BME688 telemetry encoded as Protobuf"
},
"format": "protobuf",
"schema": {
"messageName": "maps.sensors.Bme688Reading",
"descriptorValue": "<base64-encoded FileDescriptorSet bytes>"
}
}
4.1 Fields
format: must be"protobuf".schema.messageName: the fully-qualified Protobuf message name for the root message.schema.descriptorValue: base64 encoding of the descriptor bytes used to construct the Protobuf descriptors at runtime.
5. Java Integration (Conceptual)
At runtime, MAPS:
- Looks up the
SchemaConfigfor the topic/context. - Deserialises the
schemaobject intoProtobufConfig. - Decodes
descriptorValuefrom base64 into abyte[]. - Builds a descriptor set from this byte array.
- Locates the root message type using
messageName. - Decodes or encodes Protobuf messages based on that descriptor.
The result is exposed to the rest of MAPS as a Typed Event.
6. Event Flow with Protobuf
6.1 Decoding
- Incoming payload: binary Protobuf message on a topic bound to a Protobuf schema.
- MAPS:
- finds the Protobuf
SchemaConfig - loads/uses the
ProtobufConfig - decodes the bytes into a dynamic Protobuf message
- converts it into a Typed Event
- finds the Protobuf
- The Typed Event flows through:
- filtering
- transformations
- statistics
- format conversion (for example, Protobuf → JSON / Avro / CSV)
6.2 Encoding
For outbound conversions:
- MAPS starts from a Typed Event.
- Maps fields into the Protobuf message structure defined by the descriptors.
- Builds a Protobuf message dynamically.
- Serialises it to a binary Protobuf payload.
7. Protobuf-Specific Notes
7.1 Strong Typing and Evolution
- Protobuf identifies fields by numeric tags.
- Schemas can evolve (add/remove fields) while remaining compatible.
- MAPS uses
versionIdandlabels.uniqueIdto track versions on the schema side.
7.2 Enums
- Protobuf enums are mapped to typed fields in the Typed Event.
- MAPS can preserve both the enum name and underlying numeric value.
7.3 Timestamps
Typical patterns:
int64epoch millisgoogle.protobuf.Timestamp
Internally, MAPS normalises timestamps into a standard representation (for example, epoch millis) for filtering and statistics.
7.4 Oneof
Protobuf oneof fields are treated as mutually exclusive fields in the Typed Event:
- only the active field is populated
- inactive members are omitted or set to null
7.5 Nested / Embedded Messages
Nested messages become nested objects in the Typed Event and can participate in:
- filtering
- transformation rules
- statistics aggregation
8. When to Use Protobuf in MAPS
Protobuf is a good fit when you need:
- strict schemas and type checking
- compatibility across languages and platforms
- compact and efficient binary encoding
- more expressive structure than CSV or Native, but with smaller size than JSON/XML
For very simple single-value payloads, prefer Native schemas.
For JSON-centric systems or ad-hoc APIs, JSON schemas may be simpler to manage.
9. Example
This example loads a Protobuf descriptor and constructs a ProtoBufSchemaConfig for use
public static SchemaConfig getProtobufSchema(byte[] protoDesc, String messageName, String title, String description, String matcher, String type) {
UUID schemaId;
try {
schemaId = UuidGenerator.getInstance().generate(NamedVersions.SHA1,uuid, title );
} catch (NoSuchAlgorithmException e) {
schemaId = UuidGenerator.getInstance().generate();
}
ProtoBufSchemaConfig config = new ProtoBufSchemaConfig();
ProtoBufSchemaConfig.ProtobufConfig protoConfig = new ProtoBufSchemaConfig.ProtobufConfig();
protoConfig.setDescriptorValue(protoDesc);
protoConfig.setMessageName(messageName);
config.setProtobufConfig(protoConfig);
config.setComments(description);
config.setTitle(title);
config.setVersion(1);
config.setMatchExpression(matcher);
config.setUniqueId(schemaId);
config.setResourceType(type);
return config;
}
Example protobuf config
syntax = "proto3";
package aircraft;
option java_package = "io.mapsmessaging.proto.aircraft";
option java_outer_classname = "SensorReadingProto";
message SensorReading {
string id = 1; // Optional sensor or reading ID (e.g., "egt-sensor-1")
uint32 engine_id = 2; // Engine number (e.g., 1, 2, ...)
float value = 3; // The measured value
string unit = 4; // Unit of measurement (e.g., "C", "RPM", "PSI")
int64 timestamp = 5; // Epoch timestamp in milliseconds
}
syntax = "proto3";
package aircraft;
option java_package = "io.mapsmessaging.proto.aircraft";
option java_outer_classname = "FuelSystemProto";
message FuelSystem {
float fuel_quantity_left_kg = 1;
float fuel_quantity_right_kg = 2;
float fuel_temperature_c = 3;
bool crossfeed_enabled = 4;
}