gRPC Error Handling
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
- What are the most common gRPC status codes and their meanings?
- How do you add structured error details to a gRPC error response?
- What is the difference between
status.Errorandstatus.Errorf? - How should clients handle UNAVAILABLE vs INVALID_ARGUMENT errors?
- 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
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