Skip to content
datahacks.dev
XML

XML Versus Protocol Buffers for Services and Messages

50,000 orders in four formats: protobuf was 5.1 MB, JSON 10.8 MB, XML 14.2 MB, and it parsed in 9 ms against 161 for XML. What that buys and costs.

5 min read
Summarize in AI
Flat illustration of two options weighed side by side, with the XML logo.

Teams that run services talk to each other thousands of times a second, and at some point someone asks whether the messages should be XML at all. Protocol Buffers is the usual alternative: a compact binary encoding with a schema compiler. The claims on both sides are loud. This post measures one realistic message in four formats and then lays out what the numbers do and do not decide.

The message

An order with an id, a status, a customer, a date and one to five lines of SKU, quantity and price. In protobuf, the schema is:

order.protoprotobuf
syntax = "proto3";
message Line { string sku = 1; int32 qty = 2; string price = 3; }
message Order { int32 id = 1; string status = 2; string customer = 3; string placed = 4; repeated Line lines = 5; }
message Orders { repeated Order orders = 1; }

The same data was serialized to compact JSON, YAML and XML (attributes for id, status, sku and qty; elements for the rest). The prices are strings in protobuf, as in the XML, to keep the comparison on equal terms.

Size and speed

Text
format          bytes      gzip
XML          14169433   1812278
JSON         10817478   1738086
YAML         11418224   1769046
protobuf      5068788   1642380

parse ms (best of 3), 50,000 orders:
  XML lxml     161
  JSON json    132
  YAML CLoader 7238
  protobuf     9

Three things stand out.

Uncompressed, protobuf is a third of XML's size. It does not write field names, only small numeric tags, and it packs integers in variable-length form. The gap to JSON is about half.

Compressed, the sizes converge. With gzip, the XML is 1.81 MB and the protobuf 1.64 MB, 10 percent apart. Repeated names compress almost to nothing, which is the same effect that makes XML tolerable on the wire. If your services already compress responses, the bandwidth argument for protobuf is weaker than the uncompressed numbers suggest.

Parsing is where the difference is real. Protobuf parsed 50,000 orders in 9 ms, XML in 161 ms, 18 times slower. A binary format reads fixed tags and lengths, and a text format must be tokenized and the names matched. The YAML number, 7.2 seconds, is the C loader's cost for a much richer grammar, and a reason not to use YAML for bulk data. The speed gap matters at volume: a service handling 10,000 messages a second spends noticeable CPU on XML parsing and almost none on protobuf.

These are Python numbers, and absolute values differ by language. In Java or Go the XML parsers are faster in absolute terms and protobuf still wins by a similar factor. Measure in your stack with your messages before deciding.

What you give up

Protobuf's speed and size come from two design decisions, and both have costs.

Messages do not describe themselves. A protobuf message is bytes with numeric field tags. Without the .proto file, you cannot read it, and with a stale one you read the wrong fields. A person cannot open it in an editor, curl it, or paste it into a ticket. Debugging needs tools (protoc --decode) and the schema at hand. XML is the opposite: a stranger can open the file, read the names and often work out the meaning.

Both sides need generated code and the schema in sync. That is a feature inside one organization, where a compiler checks that sender and receiver agree and a registry (Buf, a schema repository) governs evolution. It is a burden for external partners, who must adopt your toolchain. XML plus an XSD can be consumed by whatever the partner already has.

Evolution works differently too. Protobuf is designed for compatible change: you add fields with new numbers, never reuse a number, and old readers skip fields they do not know. XML achieves the same with a "must ignore" policy and an extension point (versioning an XML format without breaking readers). Protobuf makes the rules mechanical, XML makes them a convention you write down.

A comparison by question

QuestionProtobufXML
Human-readable on the wireNoYes
Smaller uncompressedYes, about a thirdNo
Smaller compressedSlightlySlightly larger
Parse speedMuch fasterSlower
Schema required to readYesNo (helps, not required)
Mixed content and documentsNot designed forYes
Streaming of large filesLength-delimited messagesYes, mature
Browser and partner friendlyNeeds toolingEverywhere
Standards ecosystemgRPC, BufXSD, XSLT, XPath, signatures

Read the table as two different jobs. Protobuf is a good encoding for a function call between two programs you maintain. XML is a good encoding for a document that travels between organizations and has to outlive the programs that wrote it.

A practical split

Many systems end up with both, deliberately. Inside the platform, services call each other with protobuf over gRPC for the speed and the compile-time checks. At the edge, where partners and regulators expect their format, the system accepts and produces XML (or JSON) and translates at the boundary. The translation layer is one service that owns the mapping, which keeps the XML-specific problems out of the core. If you add that layer, give it the corpus and mapping tests described in moving from XML to JSON gracefully.

If you have XML services today and the only reason to change is a feeling, do not. Measure the CPU spent in parsing under real load first. If it is under ten percent, the migration buys little. If parsing dominates, check the cheaper fixes: compression, caching, streaming, and a faster parser.

The cluster hub is choosing XML over the alternatives, or not. For the legacy side, see migrating a legacy XML interface to JSON and keeping both XML and JSON output from one source. The encoding is specified in the Protocol Buffers documentation, and the guide to schema evolution in the proto3 language guide. Inspect a sample XML message in the XML formatter.

When Protocol Buffers are not the better fit

Documents with mixed content. Protobuf has no concept of text with inline markup. Use XML.

Messages people must read or edit. Configuration, contracts and anything a human signs off belong in a text format.

External parties you cannot equip. If your integration partners are small shops with an XML toolchain, asking them to compile your schema is a cost they will price into the project.

Long-lived archives. A format that requires the original schema and compiler to be readable in twenty years is a risk. XML in a plain text file, with its schema alongside, will still open.

Low volume. At a few requests a second, the parsing time is invisible and the readability of the wire format is worth more than its speed.

Also inTooling
Share

Keep reading

Get new articles by email

We send one email when a new article is published. No spam.

Unsubscribe in one click. No spam, ever.