惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
D
DataBreaches.Net
Microsoft Azure Blog
Microsoft Azure Blog
大猫的无限游戏
大猫的无限游戏
雷峰网
雷峰网
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Privacy & Cybersecurity Law Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
L
LINUX DO - 最新话题
L
LangChain Blog
量子位
P
Palo Alto Networks Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
L
Lohrmann on Cybersecurity
博客园 - 聂微东
人人都是产品经理
人人都是产品经理
Y
Y Combinator Blog
MongoDB | Blog
MongoDB | Blog
PCI Perspectives
PCI Perspectives
S
SegmentFault 最新的问题
O
OpenAI News
S
Securelist
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
C
Cybersecurity and Infrastructure Security Agency CISA
AWS News Blog
AWS News Blog
G
Google Developers Blog
博客园 - 叶小钗
C
Check Point Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Cisco Talos Blog
Cisco Talos Blog
罗磊的独立博客
V2EX - 技术
V2EX - 技术
小众软件
小众软件
IT之家
IT之家
Engineering at Meta
Engineering at Meta
Hacker News - Newest:
Hacker News - Newest: "LLM"
M
MIT News - Artificial intelligence
T
Threat Research - Cisco Blogs
Vercel News
Vercel News
酷 壳 – CoolShell
酷 壳 – CoolShell
A
About on SuperTechFans
Recorded Future
Recorded Future
N
News and Events Feed by Topic
Cloudbric
Cloudbric
W
WeLiveSecurity
T
The Exploit Database - CXSecurity.com
Martin Fowler
Martin Fowler
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Cloudflare Blog
宝玉的分享
宝玉的分享

kmcd.dev

Beating Go gRPC-Web Should Have Fixed gRPC Making Dynamic Protobuf Fast in Go Proxy, Record, and Mock gRPC APIs with FauxRPC Exploring Protocol Buffers Interactively Introducing ProtoDocs Ghost in the Shell: The Manga Behind the Anime The Hidden Cost of google.protobuf.Value Why Networking Built Its Own Data Modeling Language Zero-Friction Demos with WASM Let's Learn About BGP ConnectRPC: Where is it now? Building APIs with Contracts The Case for Greppable Code Unknown Fields in Protobuf IRC Log: Reactionary Faking protobuf data in Go Y'all are Sleeping on Mise-en-Place IRC Log: Standup 2 HTTP/2 From Scratch: Part 4 IRC Log: rm -rf /var/opt/gitlab/postgresql/data HTTP/2 From Scratch: Part 3 Building a Live BGP Map HTTP/2 From Scratch: Part 2 IRC Log: The Cloud Scale Incident Visualizing the Internet (2026) Shell Log: Namaste HTTP/2 From Scratch: Part 1 IRC Log: Standup HTTP/1.1 From Scratch WHOIS is dead, long live RDAP Months Considered Harmful Encryption vs. Compression On Creating My Own Cover Art Traceroute Tool from Scratch in Go My Favorite Interview Question From JSON to Protobuf Morse Code Can You Hack a Phone with Your Voice? Visualizing the Internet (2025) HTTP QUERY and Go I made a daily word game Protovalidate: Can Input Validation Be This Easy? Behold! The Barcode Scanner Mixing CEL and Protobuf for Fun FauxRPC and Protovalidate The Call of the Monolithic Codebase FauxRPC + Test Containers Self-Documenting Connect Services gRPC Over HTTP/3: Followup JSON to Protobuf Conversion gRPC: The Ugly Parts Working with Protobuf in 2024 Introducing FauxRPC HTTP/1.0 From Scratch Y'all are sleeping on HTTP/3 HTTP/0.9 From Scratch What version of HTTP are you using? Texans in Denmark gRPC Over HTTP/3 gRPC: The Good Parts Leaving Texas for Greener Pastures gRPC: The Bad Parts Unit Testing ConnectRPC Servers Daily Prompts Adding chart.js to Hugo Why I'm Rebranding Benchmarking gRPC (golang) Blog Update gRPC From Scratch: Part 3 - Protobuf Encoding Tracking the Wins Visualizing the Internet (2024) Dropping Unknown Fields in ConnectRPC RESTless: Web APIs After REST Introducing unknownconnect-go Making gRPC more approachable with ConnectRPC Inspecting Protobuf Messages Introducing protoc-gen-connect-openapi gRPC From Scratch: Part 2 - Server gRPC From Scratch: Part 1 - Client Why you should use gNMI over SNMP in 2026 The Rollercoaster of Productivity in Side Projects Lessons from a Decades-Long Project How I learned to code Economists with (virtual) Guns Visualizing the Internet (2023) softlayer-python: language bindings/CLI for a cloud company SwFTP: SFTP/FTP Server For Openstack Swift Video: Morning Copenhagen Commute Goodbye Evepraisal Visualizing the spectrum of the sun (Part 2) Visualizing the Internet (2022) Evepraisal: A price estimation tool for Eve Online Visualizing the spectrum of the sun
Breaking gRPC
2025-08-05 · via kmcd.dev

When we use gRPC, we often praise its efficiency and strong contracts defined by Protocol Buffers (.proto files). We know that gRPC uses protobuf’s binary format for fast, compact, and forward/backward-compatible communication. But what happens when you expose your gRPC service to clients who speak JSON, like a web frontend?

The encoding you use (binary protobuf or transcoded JSON) dramatically changes the rules of what constitutes a “safe” or “breaking” change to your API. A change that is perfectly harmless for a protobuf client can completely break a JSON client. Let’s dig into this more.

How Encodings Work

First, a quick refresher on how each format represents data. Consider this simple protobuf message:

syntax = "proto3";

package my_service.v1;

message User {
  // A unique identifier for the user.
  int64 user_id = 1;
  // The user's full name.
  string name = 2;
}

Protobuf: It’s All About the Numbers

On the wire, the binary protobuf encoding doesn’t care about the field names (user_id, name). It only cares about the field numbers (1, 2) and their wire types. A simplified view of the encoded data is a series of key-value pairs where the key is the field number. I dig into this further in my gRPC from Scratch series, where I discuss the binary protobuf encoding.

Because of this, you can rename a field in your .proto file, and as long as the field number and type remain the same, it’s a non-breaking change for protobuf clients.

JSON: It’s All About the Names

When a gRPC gateway or library transcodes this message to JSON, it produces a standard JSON object. JSON is also a perfectly valid encoding to use with gRPC. By default, it uses the protobuf field names (converted to lowerCamelCase) as the JSON keys:

{
  "userId": 12345,
  "name": "Alex"
}

Since JSON clients are coupled to these names, changing them will inevitably break the integration. JSON clients are coupled to field names, not field numbers. This fundamental difference is the source of many potential compatibility issues.

Analyzing API Changes: Breaking vs. Non-Breaking

Let’s look at common changes you might make to a .proto file and see their impact on each encoding.

ChangeProtobuf ImpactJSON ImpactExplanation
Renaming a field (name to full_name)Non-breaking💥 BreakingProtobuf clients only see the field number (2), which hasn’t changed. JSON clients expect the key "name" but will now see "fullName".
Changing a field number (= 2 to = 3)💥 BreakingNon-breakingThis is a cardinal sin in the protobuf world. A client expecting field 2 will no longer find it. JSON clients, however, still see the key "name" and are unaffected.
Adding a new field (email = 3)Non-breakingNon-breakingWell-behaved clients in both formats are designed to ignore unknown fields, making this a safe operation.
Removing or deprecating a fieldNon-breakingNon-breakingSimilar to adding a field, clients should handle missing fields gracefully. It’s best practice to deprecate a field before removing it.
Changing a compatible type (int32 to int64)Non-breakingNon-breakingThese types have compatible wire formats in protobuf. For JSON, both are simply numbers, so there’s no issue.
Changing an incompatible type (int64 to string)💥 Breaking💥 BreakingThe wire format for a number and a string are different, breaking protobuf clients. The data type in JSON also changes (e.g., 123 vs. "123"), which will break any client expecting a number.

The Solution: Decouple Names with json_name

So, how do you refactor your .proto field names without breaking your JSON clients? The protobuf specification provides a simple and elegant solution: the json_name field option.

This option lets you explicitly set the JSON key for a field, decoupling it from the .proto field name.

Let’s revise our User message. Suppose we want to rename name to full_name for clarity in our Go or Python code, but we can’t break existing JSON clients that rely on the "name" key.

syntax = "proto3";

package my_service.v1;

message User {
  int64 user_id = 1 [json_name = "userId"];

  // The field is now 'full_name' in code, but will still be 'name' in JSON.
  string full_name = 2 [json_name = "name"];
}

With json_name = "name", we’ve instructed the transcoder to do the following:

  1. For Protobuf: Continue using field number 2. The field name full_name is used by the code generator.
  2. For JSON: Always use the key "name" during serialization, regardless of what the .proto field is called.

Now, you are free to change the full_name field to something else (e.g., user_display_name) in the future, and your JSON contract remains stable.

Automating Your Safety Net with buf breaking

Remembering all these nuanced rules across different encodings is difficult and error-prone. This is where automated tooling becomes essential. The popular Buf toolchain includes a powerful command, buf breaking, designed specifically for this problem.

The buf breaking command compares your current .proto files against a previous state (like your main git branch) and reports any changes that would break your API consumers. Crucially, it understands that “breaking” means different things to different clients. You can configure it to check against multiple compatibility strategies.

In your buf.yaml configuration file, you can specify which rule sets to check against:

  • FILE: Checks for backward-incompatible changes at the .proto file level, like deleting a field or changing a field number. This protects your protobuf-based clients.
  • WIRE_JSON: Checks for backward-incompatible changes for the JSON wire format. This catches things like renaming a field without using json_name. This protects your JSON-based clients.
  • PACKAGE: Checks for source-code-level breaking changes in the generated stubs for languages like Go and Java. This protects the developers using your generated code.

A typical configuration for a service with both gRPC and JSON clients might look like this:

# buf.yaml
version: v2
breaking:
  use:
    - FILE
    - WIRE_JSON

By integrating buf breaking into your CI/CD pipeline, you can automatically prevent developers from merging changes that would break any of your consumers, whether they speak protobuf or JSON.

Conclusion

Evolving an API for both protobuf and JSON clients is a recipe for a very specific kind of headache, the kind that pages you at 3 AM. You’ve got protobuf, which only cares about numbers, and JSON, which only cares about names. A “safe” refactor for one is a production-breaking slap in the face for the other. This is where a schema-first approach, backed by powerful schema-aware tooling, isn’t just a good idea; it’s the only thing keeping you from questioning all your life choices.

Protobuf’s semantics, like the json_name option, give you a powerful escape hatch. It makes certain refactors, like renaming a field for internal clarity, trivial if you have the right tooling in place. You can change your code without your JSON clients ever knowing you touched a thing. This decoupling is a superpower, but only if you use it correctly.

And that’s the catch: don’t rely on developers’ goldfish sized memory or manual code reviews to enforce these complex, conflicting rules. That’s how you break production at 3 AM. Instead, let the robots do the heavy lifting. Integrating a tool like buf breaking into your CI pipeline is like having an unblinking, unforgiving guardian for your API. It understands the different breaking change rules for both protobuf and JSON and will stop a bad change before it ever gets merged. This is the real strength of a schema-first workflow: it makes complex refactors not just possible, but safe. You can merge with confidence and keep all your clients (binary or JSON) happy.