Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
20 Jul 2025 · 5 min read ·Article 42 / 110
Go

42 Using Enums in Protobuf

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

When we build modern distributed applications, data communication between services becomes crucial, especially when our microservices are written in different programming languages. Protobuf, or Protocol Buffers, from Google, has become one of the top choices for data serialization. One of the interesting features of Protobuf is the enum, which is often overlooked but is extremely vital for keeping data consistent and type-safe.

This article will take a deep dive into how to use enums in Protobuf, complete with code examples, usage simulations, and implementation best practices. I’ll also point out a few anti-patterns that commonly come up so you’ll be better prepared when implementing them.


Why Are Enums Important in Protobuf?

Let’s start with a simple understanding: enums help you restrict the values that can be assigned to a field to a fixed set of options, making your code safer and easier to maintain.

In general, enums in protobuf:

  • Improve code readability
  • Make automatic data validation easier
  • Ensure data integrity across programming languages
  • Reduce the risk of invalid values, since the integers are handled in a standardized way

Defining Enums in Protobuf

A Simple Enum Example

Let’s look at how it’s defined in a Protobuf file:

protobuf
 1syntax = "proto3";
 2
 3package user;
 4
 5enum UserRole {
 6  USER_ROLE_UNSPECIFIED = 0; // Convention: use 0 as the default for an undefined value
 7  USER_ROLE_ADMIN = 1;
 8  USER_ROLE_MEMBER = 2;
 9  USER_ROLE_GUEST = 3;
10}
11
12message User {
13  string id = 1;
14  string name = 2;
15  UserRole role = 3;
16}

Explanation

  • USER_ROLE_UNSPECIFIED is always recommended to be the value 0. This is important, because if the value is not set, Protobuf will automatically take 0 as the default.
  • Each enum member is given a unique integer value.
  • This enum can be used inside a message as a data type, like role above.

Enum Mapping Across Different Languages

Let’s see how the enum from the proto code above can be compiled and used in various languages:

LanguageEnum Representation
Gouser.UserRole_USER_ROLE_ADMIN
JavaUser.UserRole.USER_ROLE_ADMIN
PythonUserRole.USER_ROLE_ADMIN
TypescriptUserRole.USER_ROLE_ADMIN (in the generated d.ts file)


Simulation: Using Enums in a User Service Application

Say we have a simple microservice for users. We want to fetch all users with a certain role. You’ll see how enums really help keep code consistent on both the client and server side.

Request-Response Service Simulation

Protobuf File

protobuf
 1service UserService {
 2  rpc GetUsersByRole (GetUsersByRoleRequest) returns (GetUsersByRoleResponse);
 3}
 4
 5message GetUsersByRoleRequest {
 6  UserRole role = 1;
 7}
 8
 9message GetUsersByRoleResponse {
10  repeated User users = 1;
11}

Service Implementation (Go example)

go
1func (s *UserService) GetUsersByRole(ctx context.Context, req *userpb.GetUsersByRoleRequest) (*userpb.GetUsersByRoleResponse, error) {
2    var filtered []*userpb.User
3    for _, u := range s.users {
4        if u.Role == req.Role {
5            filtered = append(filtered, u)
6        }
7    }
8    return &userpb.GetUsersByRoleResponse{Users: filtered}, nil
9}

Calling It from the Client

python
1from user_pb2 import GetUsersByRoleRequest, UserRole
2
3request = GetUsersByRoleRequest(role=UserRole.USER_ROLE_MEMBER)
4response = stub.GetUsersByRole(request)
5print(response.users)

Every role value is always certain, it can never fall outside the range 0-3.


Flow Diagram of the Enum Serialization Process

Let’s visualize how an enum works during data transmission using the following mermaid code:

MERMAID
flowchart LR
  A[Client Mengirim Request dengan Enum UserRole] --> B[Protobuf Serialisasi enum ke Integer Value]
  B --> C[Network]
  C --> D[Server Menerima Integer Value]
  D --> E[Protobuf Mapping Integer ke Enum]
  E --> F[Pengolahan Data oleh Service]

Challenges and Best Practices for Enums in Protobuf

1. Avoid Changing Existing Enum Values

Once you’ve published an enum value, never change the order or its integer value. Doing so causes data conflicts, mapping errors, or even corrupted values in older messages that have already been serialized.

2. Adding New Enum Values? Safe!

Adding a new member to an enum is compatible, as long as you don’t change the integer value of existing members. If you want to deprecate one, keep the old declaration in the proto file.

3. Reserved & Deprecated

If you want to remove an enum value, use the reserved annotation:

protobuf
1enum UserRole {
2  USER_ROLE_UNSPECIFIED = 0;
3  USER_ROLE_ADMIN = 1;
4  // reserved 2; // So that value 2 can't be reused for a new value
5  USER_ROLE_GUEST = 3;
6}

4. Default Value Zero

There is always a 0 value as the unspecified/unknown/default, so backward compatibility is preserved.

5. String Mapping and Interoperability

Sometimes you need to map an enum to a string (for example, in the frontend). The safe practice: store the enum value in the database as a number, then render it as a string for display.

Example mapping in Go:

go
 1func RoleToString(role userpb.UserRole) string {
 2    switch role {
 3    case userpb.UserRole_USER_ROLE_ADMIN:
 4        return "Admin"
 5    case userpb.UserRole_USER_ROLE_MEMBER:
 6        return "Member"
 7    case userpb.UserRole_USER_ROLE_GUEST:
 8        return "Guest"
 9    default:
10        return "Unspecified"
11    }
12}

Common Anti-Patterns

Don’t silently repurpose a reserved enum value to mean something else! For example:

protobuf
1// Wrong: abusing role 99 for "SuperAdmin"
2enum UserRole {
3  USER_ROLE_UNSPECIFIED = 0;
4  USER_ROLE_ADMIN = 1;
5  USER_ROLE_MEMBER = 2;
6  USER_ROLE_SUPER_ADMIN = 99; // Avoid jumping integers around for no reason!
7}
This will make debugging and maintainability harder, and JSON-to-proto mapping in the browser will feel unnatural.


Conclusion

Enums in Protobuf provide great power: more structured, safe, and easy-to-maintain data across languages. By understanding the best practices above and avoiding the anti-patterns, you can build a more solid and scalable application architecture without losing the flexibility to evolve your data model in the future.

Are you already using enums correctly in your Protobuf? If not, try refactoring with the examples above and feel how much easier validation and consistency become!


Further references:


Happy coding! 🚀

Related Articles

💬 Comments