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

16 Error Handling in a gRPC Server

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

16. Error Handling in a gRPC Server

In modern distributed system architectures, gRPC has become one of the go-to RPC protocols for service-to-service communication thanks to its efficiency, multi-language support, and robust transport (HTTP/2). However, like any distributed system, failures are inevitable—ranging from invalid requests and unavailable services to authentication issues. Good error handling on the gRPC server side has a huge impact on the client experience, the overall reliability of the system, and how easy it is to troubleshoot.

This article covers error-handling practices for gRPC servers based on real-world experience and the best engineering insights, complete with code examples, simulated error scenarios, and a diagram illustrating the flow.


The gRPC Error Model: Reasoning About the Architecture

gRPC builds its error standard on top of status codes from Google RPC and maps them to HTTP statuses. Every RPC can return an error object with:

  • A status code (codes)
  • An error message
  • Optional metadata

The gRPC status codes capture a wide range of common cases, such as:

CodeDescriptionHTTP Mapping
OKSuccess200
CANCELLEDThe client cancelled the request499
INVALID_ARGUMENTInvalid input data400
DEADLINE_EXCEEDEDTimeout504
NOT_FOUNDData not found404
ALREADY_EXISTSResource already exists409
PERMISSION_DENIEDNo access403
UNAUTHENTICATEDNot authenticated401
RESOURCE_EXHAUSTEDQuota exhausted429
INTERNALServer error500
UNAVAILABLEService unavailable503

For the full list, check out gRPC Status Codes .


Example Error-Handling Implementation in a gRPC Server (Golang)

We’ll use Go because its gRPC ecosystem is mature and widely adopted by backend engineers.

Suppose we have a UserService with a GetUser RPC whose purpose is to fetch user data by id.

Protobuf definition:

protobuf
 1service UserService {
 2  rpc GetUser(GetUserRequest) returns (UserResponse);
 3}
 4
 5message GetUserRequest {
 6  string id = 1;
 7}
 8
 9message UserResponse {
10  string id = 1;
11  string name = 2;
12  string email = 3;
13}

gRPC Server with Error Handling

go
 1import (
 2    "context"
 3    "errors"
 4
 5    pb "github.com/company/proto"
 6    "google.golang.org/grpc/codes"
 7    "google.golang.org/grpc/status"
 8)
 9
10type UserServiceServer struct {
11    pb.UnimplementedUserServiceServer
12    users map[string]*pb.UserResponse
13}
14
15func NewUserServiceServer() *UserServiceServer {
16    return &UserServiceServer{
17        users: map[string]*pb.UserResponse{
18            "1": {Id: "1", Name: "Budi", Email: "budi@email.com"},
19        },
20    }
21}
22
23func (s *UserServiceServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.UserResponse, error) {
24    if req.GetId() == "" {
25        // Simulate invalid input
26        return nil, status.Error(codes.InvalidArgument, "user id must not be empty")
27    }
28
29    user, ok := s.users[req.GetId()]
30    if !ok {
31        // Simulate data not found
32        return nil, status.Error(codes.NotFound, "user not found")
33    }
34
35    // Simulate an internal error
36    if req.GetId() == "999" {
37        return nil, status.Errorf(codes.Internal, "internal error: %v", errors.New("simulated panic"))
38    }
39
40    return user, nil
41}

Notes:

  • Errors are wrapped with status.Error(codes.XXXX, "error message").
  • Try to make error messages clear (not just “error occurred”).
  • Map errors according to the root cause—don’t just default to INTERNAL.

Simulating Error Scenarios

Let’s test 3 error scenarios:

  1. Invalid input: empty id.
  2. User not found: the id does not exist in the database.
  3. Internal error: id = “999”, triggering a system error.

Response Simulation Table

RequestError CodeError MessageHTTP
{id: ""}INVALID_ARGUMENTuser id must not be empty400
{id: "2"}NOT_FOUNDuser not found404
{id: "999"}INTERNALinternal error: simulated panic500

Diagram of the gRPC Error-Handling Flow on the Server

Let’s illustrate the error-handling process for a single RPC endpoint:

MERMAID
flowchart TD
    A[Client kirim request GetUser] --> B{Validasi Input?}
    B -- No --> E[Return INVALID_ARGUMENT]
    B -- Yes --> C{User Ada?}
    C -- No --> F[Return NOT_FOUND]
    C -- Yes --> D{Terjadi Error Internal?}
    D -- Yes --> G[Return INTERNAL]
    D -- No --> H[Kirim Response Sukses]

How Do You Attach Metadata to an Error?

We often want to send additional metadata with an error—for example, which field failed validation.

gRPC supports the use of an error details extension:

go
 1import "google.golang.org/genproto/googleapis/rpc/errdetails"
 2
 3// Return details with the error
 4badRequest := &errdetails.BadRequest{
 5    FieldViolations: []*errdetails.BadRequest_FieldViolation{
 6        {
 7            Field:       "id",
 8            Description: "id must not be empty",
 9        },
10    },
11}
12
13st := status.New(codes.InvalidArgument, "invalid input")
14st, _ = st.WithDetails(badRequest)
15return nil, st.Err()

On the client side, we can parse the details and present a more informative error.


Best Practices for Error Handling in a gRPC Server

  1. Precise Mapping: Use the correct error code for the context, not just a blanket INTERNAL.
  2. Human-Readable Messages: Error messages should be clear, non-misleading, and safe from leaking sensitive data.
  3. Don’t Leak Internal Info: In production, never expose stack traces or DB queries to the client.
  4. Use Metadata/Details: Add error details when needed for complex validation cases.
  5. Standardize Handling: Build an error middleware/interceptor so handling is consistent across all endpoints.
  6. Log Internal Errors: Always log internal errors in detail for investigation without leaking them to the user.
  7. Graceful Handling: Avoid panics; handle errors within a structured flow.
  8. Test Error Paths: Don’t just test the happy path—make sure every error scenario is covered.

Closing

Error handling in a gRPC server is not merely about returning an error; it’s about clear communication between systems and engineers. With proper error mapping, clear messages, and metadata extensions on errors, our gRPC system becomes more robust, maintainable, and observable.
Remember, an error that isn’t handled well can be more dangerous than the error itself.

And don’t forget—an error is a form of communication. Let’s make it meaningful!


References:


Thanks for reading! Share your gRPC error-handling experiences or tips in the comments. 🚀

Related Articles

💬 Comments