Skip to content

gRPC Error Handling

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about grpc error handling. We cover key concepts, practical examples, and best practices to help you master this topic.

Error handling in gRPC uses a set of well-defined status codes combined with optional error details. This lesson covers standard error patterns, propagating errors across services, and building resilient clients.

What You'll Learn

  • Understanding gRPC status codes
  • Returning errors from server implementations
  • Handling errors on the client side
  • Using rich error details with google.rpc.Status
  • Error propagation in service chains

Why It Matters

Proper error handling improves debuggability, reliability, and developer experience. gRPC's structured error model allows clients to programmatically handle different failure modes.

Real-World Use

A payment processing system uses gRPC error details to communicate specific failure reasons. When a payment is declined, the server returns a structured error with the decline reason, retry eligibility, and suggested alternative payment methods.

Flow Chart

flowchart LR
    A[Server] --> B{Error Occurs}
    B --> C[Status Code]
    B --> D[Error Message]
    B --> E[Error Details]
    C --> F[Client Receives]
    D --> F
    E --> F
    F --> G{Handle Error}
    G -->|Retryable| H[Retry with Backoff]
    G -->|Fatal| I[Fail Fast]
    G -->|Unavailable| J[Failover]

Code Examples

Example 1: Returning Errors in Go Server

package main

import (
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
    "google.golang.org/genproto/googleapis/rpc/errdetails"
)

func (s *server) GetUser(ctx context.Context,
    req *pb.GetUserRequest) (*pb.User, error) {
    
    if len(req.UserId) == 0 {
        st := status.New(codes.InvalidArgument,
            "user_id is required")

        details := &errdetails.BadRequest_FieldViolation{
            Field:       "user_id",
            Description: "User ID must not be empty",
        }

        badRequest := &errdetails.BadRequest{}
        badRequest.FieldViolations = append(
            badRequest.FieldViolations, details)

        st, _ = st.WithDetails(badRequest)
        return nil, st.Err()
    }

    user, err := db.FindUser(req.UserId)
    if err == sql.ErrNoRows {
        return nil, status.Error(
            codes.NotFound,
            "user not found")
    }
    if err != nil {
        return nil, status.Error(
            codes.Internal,
            "database error")
    }

    return user, nil
}

Expected output: Client receives appropriate status codes and structured error details for validation failures.

Example 2: Client Error Handling in Python

import grpc
from google.rpc import error_details_pb2

def get_user(client, user_id):
    try:
        response = client.GetUser(
            pb.GetUserRequest(user_id=user_id))
        return response
    except grpc.RpcError as e:
        if e.code() == grpc.StatusCode.NOT_FOUND:
            print(f"User {user_id} not found")
            return None
        elif e.code() == grpc.StatusCode.INVALID_ARGUMENT:
            for detail in e.details():
                if isinstance(detail,
                    error_details_pb2.BadRequest):
                    for violation in \
                        detail.field_violations:
                        print(
                            f"Field {violation.field}: "
                            f"{violation.description}")
            return None
        elif e.code() == grpc.StatusCode.UNAVAILABLE:
            print("Service unavailable, retrying...")
            return retry_with_backoff(client, user_id)
        else:
            raise

user = get_user(client, "")
# Output: Field user_id: User ID must not be empty

Expected output: Client prints specific error details for invalid arguments, handles not found gracefully, and retries on unavailable.

Example 3: Error Propagation with Metadata in Node.js

const grpc = require('@grpc/grpc-js');
const { StatusBuilder } = require('@grpc/grpc-js');

function sayHello(call, callback) {
  const metadata = new grpc.Metadata();
  metadata.set('error-code', 'RATE_LIMITED');
  metadata.set('retry-after', '30');

  const error = {
    code: grpc.status.RESOURCE_EXHAUSTED,
    details: 'Too many requests',
    metadata: metadata,
  };
  callback(error, null);
}

// Client handling
const client = new GreeterClient(
  'localhost:50051',
  grpc.credentials.createInsecure());

client.SayHello({name: 'Alice'},
  (error, response) => {
    if (error) {
      console.log(`Code: ${error.code}`);
      console.log(`Details: ${error.details}`);
      const retryAfter = error.metadata.get(
        'retry-after')[0];
      console.log(
        `Retry after: ${retryAfter} seconds`);
    }
  });

Expected output: Client receives structured error with custom metadata indicating rate limit status and retry timing.

Common Mistakes

Mistake Explanation
Using generic INTERNAL errors Always use specific status codes to help clients handle errors appropriately
Exposing sensitive details in errors Error messages should not leak stack traces, SQL queries, or internal paths
Not handling UNAVAILABLE errors Clients should retry UNAVAILABLE with backoff instead of immediately failing
Ignoring error details Rich error details provide actionable information beyond the status code
Returning errors without context Include correlation IDs or request identifiers in error metadata
Forgetting to check cancellation Context cancellation can cause false positive error logs

Practice Questions

  1. What are the most common gRPC status codes and their meanings?
  2. How do you add structured error details to a gRPC error response?
  3. What is the difference between status.Error and status.Errorf?
  4. How should clients handle UNAVAILABLE vs INVALID_ARGUMENT errors?
  5. Can you attach custom metadata to gRPC errors?

Challenge

Create an error handling middleware for a gRPC service that intercepts all panics and errors, converts them to appropriate status codes, logs structured details, and adds correlation IDs. Include a client that categorizes errors by severity and implements appropriate retry logic.

FAQ

What gRPC status code should I use for validation errors?

Use INVALID_ARGUMENT for validation failures. For missing required fields, combine it with BadRequest error details describing each violation.

How do I propagate errors across service boundaries?

When service A calls service B and B returns an error, A should generally wrap and forward the error to its own caller rather than swallowing it.

Can I use custom error codes in gRPC?

gRPC defines 17 standard status codes. You should not add custom codes; instead, use error details or metadata to convey additional information.

How do I distinguish between client and server errors?

Codes in the 4xx range (INVALID_ARGUMENT, NOT_FOUND, PERMISSION_DENIED) are client errors; 5xx range (INTERNAL, UNAVAILABLE) are server errors.

Should I retry all gRPC errors?

No. Only retry on UNAVAILABLE, DEADLINE_EXCEEDED, and RESOURCE_EXHAUSTED. Do NOT retry on INVALID_ARGUMENT, NOT_FOUND, or PERMISSION_DENIED.

How do I log gRPC errors effectively?

Use structured logging that includes status code, error message, error details, correlation ID, service name, method name, and latency.

Mini Project

Build a gRPC service with comprehensive error handling including input validation, database error handling, Rate Limiting, and authorization errors. Create a client library that classifies errors, implements retry logic with exponential backoff, and provides meaningful error messages to end users.

What's Next

Learn about gRPC reflection for runtime service discovery

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro