Why gRPC’s Code Generation Can Be Ugly—and How to Fix It

gRPC offers high performance for microservices, but its generated protobuf code, required fields handling, and steep learning curve create ugly, hard‑to‑maintain implementations; this article examines those pain points, showcases examples, and suggests tools and best practices to mitigate them.

Radish, Keep Going!
Radish, Keep Going!
Radish, Keep Going!
Why gRPC’s Code Generation Can Be Ugly—and How to Fix It

Code Generation

The code generated from protobuf is often verbose, complex, and hard to read. Although it is not meant for manual editing, its lack of readability hurts maintainability, especially in large projects. Recent language implementations have improved but still contain rough edges.

Language‑Specific Quirks

Early protobuf and gRPC implementations frequently diverge from language‑specific conventions, particularly around HTTP handling due to the mandatory support for HTTP/2. This forced the addition of ServeHTTP() in grpc‑go, which introduces noticeable performance loss and creates a trade‑off between gRPC and the Go ecosystem.

These quirks also affect protobuf type design. For example, following Buf’s style guide, enum names should be prefixed with an upper‑snake‑case version of the enum name:

enum FooBar {
  FOO_BAR_UNSPECIFIED = 0;
  FOO_BAR_FIRST_VALUE = 1;
  FOO_BAR_SECOND_VALUE = 2;
}

This convention stems from C++ enum scoping rules and influences protobuf design across languages.

Generated Code Isn’t Fast Enough

Generated code often relies heavily on runtime reflection, which is slower than hand‑written serialization. In Go, the default generated type lacks dedicated Marshal() and Unmarshal() methods, causing serialization to use reflection.

Using the vtprotobuf plugin adds type‑specific marshal/unmarshal functions and optional memory pools, yielding a 2‑4% performance boost without code changes.

Other projects achieve larger gains by making trade‑offs, but they increase code size and complexity.

Protobuf provides the optimize_for option to control code size versus speed: option optimize_for = SPEED; – longer but faster code option optimize_for = CODE_SIZE; – smaller code option optimize_for = LITE_RUNTIME; – minimal runtime, no reflection

Required Fields

Protobuf maintainers learned that required fields caused problems, leading to the introduction of proto3, which removed required fields from the spec. The consensus is to declare fields as optional for flexibility.

To enforce required‑like semantics without required fields, libraries such as protovalidate allow annotating fields with validation rules, e.g.:

message User {
  int32 age = 1 [(buf.validate.field).required = true];
}

Steep Learning Curve

Getting started with protobuf, the toolchain, and infrastructure can be daunting, especially compared to JSON‑based APIs. Language‑specific tooling, such as Grpc.Tools for .NET, helps integrate protobuf into standard build pipelines.

Community support via Buf’s Slack and other forums eases adoption, but the perception that gRPC is a “backend‑only” technology hinders its use in front‑end development.

Historical Context

gRPC was originally designed for microservices and tightly coupled to HTTP/2, which limited its adoption on the web. Even with gRPC‑Web, many front‑end libraries lack first‑class integration.

Missing Pieces

Package Management

Sharing protobuf definitions across repositories is challenging without dedicated package managers. Tools like Bazel, Pants, and Buf’s BSR help, but many projects resort to custom scripts.

Editor Support

IDE integration for generated code is limited. Better editor support, such as linking generated code back to its source protobuf, would improve developer experience.

Ugly Documentation

Documentation generated from protobuf is often unattractive. Custom protoc plugins (e.g., protoc-gen-doc) can produce OpenAPI specs and nicer docs, but the ecosystem still lags behind REST tooling.

Conclusion

gRPC is powerful but still has growing pains: unwieldy generated code, dependency‑management challenges, and limited front‑end tooling. Ongoing community efforts—such as the buf CLI, protovalidate, and protoc‑gen‑connect‑openapi—aim to close these gaps and make gRPC less “ugly” and more developer‑friendly.

References

https://kmcd.dev/posts/grpc-the-ugly-parts/

https://kmcd.dev/series/grpc-the-good-and-the-bad/

https://github.com/planetscale/vtprotobuf

https://buf.build/docs/best-practices/style-guide

https://github.com/bufbuild/protovalidate

https://grpc.io/blog/state-of-grpc-web/

https://github.com/pseudomuto/protoc-gen-doc

https://github.com/sudorandom/protoc-gen-connect-openapi

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Code GenerationBackend DevelopmentgRPCProtobuf
Radish, Keep Going!
Written by

Radish, Keep Going!

Personal sharing

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.