Kafka Protobuf to Avro Schema

Convert a .proto to an Avro schema, and get told what did not survive. Unsigned 64-bit types have no Avro equivalent, enums renumber when they are sparse, and a singular proto3 scalar has no presence to map onto a nullable union.

The root message becomes the top-level Avro record. Everything it reaches is emitted inline at its first use, and a second reference becomes a name, which is what makes a recursive message representable.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Convert. Nothing leaves this tab.

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

A proto3 message

The Avro record it becomes, and where the two type systems do not line up

syntax = "proto3";
message User {
  int32 id = 1;
  string email_address = 2;
}

An enum and a repeated field

How proto enums and repeated fields map, including the zero-value rule

syntax = "proto3";
enum Status {
  UNKNOWN = 0;
  ACTIVE = 1;
}
message Order {
  Status status = 1;
  repeated string tags = 2;
}

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Expecting proto3 optionality to survive

    Proto3 scalars have no null. A field left unset is the zero value, which is not the same as an Avro null union.

    Instead:Decide per field whether absence means null or zero, and encode that deliberately.

  2. Mapping enums directly

    Proto enums require a zero value and allow unknown values through. Avro enums are closed and reject anything unlisted.

    Instead:Map to a string, or include a default symbol.

  3. Ignoring field numbers

    Protobuf identifies fields by number and Avro by name and position. A rename is safe in protobuf and breaking in Avro.

    Instead:Keep names stable when both formats are in play.

The easy eighty per cent maps straight across, and the rest is why this page exists

Both formats describe records of typed fields, so most of a schema converts without a decision to make. A converter is worth a page for the parts that do not, because a schema the registry accepts and the data does not fit is worse than a failed conversion.

Two protobuf types have no Avro equivalent at all

Avro's long is signed 64-bit, so uint64 and fixed64 cannot be represented: every value above 9223372036854775807 has nowhere to go. Avro has no unsigned types and nothing wider, so there is no lossless choice to offer. uint32 and fixed32 have a milder version of the same problem, since Avro's int is signed 32-bit, and they widen to long, which is lossless but changes the generated Java accessor from int to long.

Avro enums have no numbers, so a sparse protobuf enum renumbers

A protobuf enum is a name-to-number mapping and the number is what goes on the wire. An Avro enum is an ordered list of symbols and the ordinal is what goes on the wire. The two agree only when the protobuf numbers happen to be 0, 1, 2 in declaration order. An enum numbered 0, 5, 9 becomes 0, 1, 2 in Avro, so every symbol after the first gap silently changes value. This is reported with the actual numbers rather than as a general warning.

Presence decides whether a field is nullable, and proto3 mostly has none

A singular proto3 scalar cannot tell unset from zero, so there is nothing to map a nullable union onto: it becomes a plain Avro field with the type's default, which is the closest true statement. A field declared optional does have presence and becomes ["null", T] with a default of null. So does a message field, and so does a wrapper type, which is the one well-known type that maps onto Avro exactly. Adding optional in the .proto and converting again is the way to get a nullable Avro field.

Timestamps lose three digits and durations have no counterpart

google.protobuf.Timestamp carries seconds plus nanoseconds. Avro's finest timestamp logical type is microseconds, so the last three digits are dropped. google.protobuf.Duration is worse: Avro's duration is a twelve-byte fixed of months, days and milliseconds, which cannot express a signed span of seconds and nanos at all, so it is converted to a record of seconds and nanos instead, which is lossless but is not Avro's own type.

The output is checked with this site's own Avro parser

Every schema this produces is parsed back before it is shown, using the same parser the Avro validator and compatibility checker on this site read through. That is a rule every generator here follows, and it is not decoration: it caught a union nested inside another union in this converter, which Avro forbids and which the wrapper types produce if the nullable wrapping does not flatten.

What this cannot see

Anything behind an import, since it reads one file. Whether the registry will accept the result under your compatibility setting, which depends on the version already registered rather than on the schema alone: the compatibility checker on this site answers that once you have both. It also does not convert data, only the schema.