English
A Complete Guide to gRPC Status Codes

Overview
In gRPC development, error handling is a key part of building robust microservice systems.google.golang.org/grpc/codes The package defines all standard gRPC status codes and is the cornerstone of error handling in Go gRPC applications.
This article will provide an in-depth analysis of codes Every status code in the package, covering its meaning, usage scenarios, code examples, and best practices for the server and client. Whether you are new to gRPC or an experienced user, I hope this article can help you better understand and use gRPC status codes.
1. What is the codes package?
codes The package is a core component of the Go gRPC framework. It defines a set of standardized status codes used in gRPC calls to represent different errors and status conditions.
1.1 Why are standardized status codes needed?
| Scene | Explanation |
|---|---|
| Cross-language unification | gRPC supports multiple languages, and standardized status codes allow services in different languages to understand each other's errors. |
| Clear semantics | Each status code has clear semantics, and the caller can implement corresponding processing logic based on the status code. |
| Debug-friendly | Standardized error codes make log tracing and problem localization more efficient. |
| Client retry strategy | The client can implement intelligent retries based on status codes (e.g.UNAVAILABLE Retryable,INVALID_ARGUMENT Not retryable) |
1.2 Import Method
go
import "google.golang.org/grpc/codes"II. Status Code Quick Reference Table
gRPC defines a total of 17 items Standard status codes that follow the status code design philosophy of HTTP/2 but have more refined semantics.
| Status code | Numerical value | meaning | Applicable scenarios |
|---|---|---|---|
OK | 0 | Success | Request processed successfully |
Canceled | 1 | The operation was canceled | The client actively cancels the request. |
Unknown | 2 | Unknown error | Unclassifiable error |
InvalidArgument | 3 | Invalid parameter | Request parameter validation failed |
DeadlineExceeded | 4 | timeout | The request exceeds the set deadline. |
NotFound | 5 | Resource does not exist | The requested resource was not found. |
AlreadyExists | 6 | Resource already exists | A conflict occurred while creating the resource |
PermissionDenied | 7 | Insufficient permissions | Insufficient permissions to perform the operation |
ResourceExhausted | 8 | Resource depletion | Insufficient quota, rate limiting triggered |
FailedPrecondition | 9 | Precondition failed | The prerequisites for the operation are not met. |
Aborted | 10 | Operation aborted | Concurrency conflict (similar to HTTP 409) |
OutOfRange | 11 | Out of range | The requested value is out of the valid range. |
Unimplemented | 12 | Not implemented | Method not implemented or not supported. |
Internal | 13 | Internal error | Internal server error |
Unavailable | 14 | Service unavailable | The service is temporarily unable to process the request. |
DataLoss | 15 | Data Loss | Unrecoverable data corruption or loss |
Unauthenticated | 16 | Unverified | The request is missing valid authentication credentials. |
💡Hint: These status codes are in codes All in the package are based on codes.OK,codes.InvalidArgument and other forms of definition.
III. Status Code Classification
For ease of memory and use, the 17 status codes can be divided into the following categories:
3.1 Success Category
| status code | Description |
|---|---|
OK | The only status code that represents success |
3.2 Client Error Class (4xx Semantics)
Caused by a problem initiated by the client; the server should not retry, and the client needs to modify the request:
| Status code | Corresponding HTTP semantics | Description |
|---|---|---|
Canceled | 499(Client Closed Request) | Client actively cancels |
InvalidArgument | 400(Bad Request) | Parameter error |
NotFound | 404(Not Found) | Resource does not exist |
AlreadyExists | 409(Conflict) | Resource already exists |
PermissionDenied | 403(Forbidden) | Insufficient permissions |
ResourceExhausted | 429(Too Many Requests) | Quota/rate limiting |
FailedPrecondition | 412(Precondition Failed) | Precondition failed |
Aborted | 409(Conflict) | Concurrency conflict |
OutOfRange | 400(Bad Request) | Parameter out of range |
Unauthenticated | 401(Unauthorized) | Unverified |
3.3 Server-side Error Category (5xx Semantics)
Caused by a server-side issue; the client can consider retrying:
| Status code | Corresponding HTTP semantics | Description | Whether retry is possible |
|---|---|---|---|
DeadlineExceeded | 504(Gateway Timeout) | Timeout | ✅ Retry after adjusting the deadline |
Unimplemented | 501(Not Implemented) | Method not implemented | ❌ Do not retry |
Internal | 500(Internal Server Error) | Internal server error | ⚠️ Retry with caution |
Unavailable | 503(Service Unavailable) | Service unavailable | ✅ Retryable (exponential backoff) |
DataLoss | 500(Internal Server Error) | Data loss | ❌ Unrecoverable, manual intervention required |
3.4 Unknown class
| Status code | Explanation |
|---|---|
Unknown | Use when the specific error type cannot be determined |
IV. Hands-on: Server Returns an Error
4.1 Basic Usage
Use status guaranteed cooperation codes Return structured error:
go
import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// User Service - Get User Information
func (s *UserService) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
// 1. Parameter validation
if req.UserId <= 0 {
return nil, status.Errorf(codes.InvalidArgument, "invalid user_id: %d", req.UserId)
}
// 2. Check if the user exists
user, err := s.repo.FindByID(req.UserId)
if err != nil {
if errors.Is(err, repo.ErrNotFound) {
return nil, status.Errorf(codes.NotFound, "user %d not found", req.UserId)
}
// Database query failed
return nil, status.Errorf(codes.Internal, "failed to query user: %v", err)
}
return &pb.GetUserResponse{User: user}, nil
}4.2 Carrying Additional Error Details
gRPC supports attaching structured error details to errors to convey more information:
go
import (
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/status"
)
func (s *UserService) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
// Validate username
if req.Username == "" {
st := status.New(codes.InvalidArgument, "invalid username")
// Add field-level error details
violation := &errdetails.BadRequest_FieldViolation{
Field: "username",
Description: "username cannot be empty",
}
badRequest := &errdetails.BadRequest{FieldViolations: []*errdetails.BadRequest_FieldViolation{violation}}
st, err := st.WithDetails(badRequest)
if err != nil {
return nil, st.Err()
}
return nil, st.Err()
}
// ... business logic
}4.3 With Timeout Control
go
func (s *OrderService) CreateOrder(ctx context.Context, req *pb.CreateOrderRequest) (*pb.CreateOrderResponse, error) {
// Set a 5-second timeout
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
// Execute order creation
order, err := s.processOrder(ctx, req)
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
return nil, status.Errorf(codes.DeadlineExceeded, "order creation timed out")
}
return nil, status.Errorf(codes.Internal, "failed to create order: %v", err)
}
return &pb.CreateOrderResponse{Order: order}, nil
}V. Hands-on: Client-Side Error Handling
5.1 Basic Error Handling
go
func callGetUser(client pb.UserServiceClient, userID int64) (*pb.GetUserResponse, error) {
ctx := context.Background()
req := &pb.GetUserRequest{UserId: userID}
resp, err := client.GetUser(ctx, req)
if err != nil {
// Parse gRPC status codes
st, ok := status.FromError(err)
if !ok {
// Non-gRPC errors (such as network errors)
return nil, fmt.Errorf("non-gRPC error: %v", err)
}
// Process according to the status code.
switch st.Code() {
case codes.InvalidArgument:
log.Warnf("invalid user_id: %s", st.Message())
return nil, fmt.Errorf("invalid request: %s", st.Message())
case codes.NotFound:
log.Warnf("user not found: %s", st.Message())
return nil, nil // Return a null value without throwing an error
case codes.Internal:
log.Errorf("server internal error: %s", st.Message())
return nil, fmt.Errorf("service temporarily unavailable, please try again later")
default:
log.Errorf("unexpected error: %v", err)
return nil, err
}
}
return resp, nil
}5.2 Intelligent Retry Strategy
go
import "github.com/grpc-ecosystem/go-grpc-middleware/retry"
// Use gRPC retry interceptor
func createRetryClient() (pb.UserServiceClient, error) {
conn, err := grpc.Dial(
"localhost:50051",
grpc.WithInsecure(),
grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(
grpc_retry.WithMax(3), // Maximum retries: 3 times
grpc_retry.WithBackoff(grpc_retry.BackoffLinear(100*time.Millisecond)),
grpc_retry.WithCodes(
codes.Unavailable,
codes.DeadlineExceeded,
codes.Internal,
),
)),
)
if err != nil {
return nil, err
}
return pb.NewUserServiceClient(conn), nil
}5.3 Manual Retry Logic
go
func callWithRetry(client pb.UserServiceClient, userID int64) (*pb.GetUserResponse, error) {
maxRetries := 3
backoff := 100 * time.Millisecond
for i := 0; i < maxRetries; i++ {
resp, err := client.GetUser(context.Background(), &pb.GetUserRequest{UserId: userID})
if err != nil {
st, ok := status.FromError(err)
if !ok {
return nil, err
}
// Only retry on specific status codes
switch st.Code() {
case codes.Unavailable, codes.DeadlineExceeded:
if i < maxRetries-1 {
log.Warnf("retry %d/%d after %v: %v", i+1, maxRetries, backoff, st.Message())
time.Sleep(backoff)
backoff *= 2 // Exponential backoff
continue
}
}
// Non-retry or retry failed
return nil, err
}
return resp, nil
}
return nil, status.Errorf(codes.Unavailable, "max retries exceeded")
}VI. Status Code Selection Guide
6.1 Decision Flowchart
6.2 Scenario Quick Reference Table
| Scene | Recommended status code | Example |
|---|---|---|
| Parameter format error | InvalidArgument | Email format is incorrect |
| Parameter value out of range | OutOfRange | The page number is negative. |
| Resource does not exist | NotFound | Failed to query user by ID |
| Create resource conflict | AlreadyExists | The username is already registered. |
| Concurrent update conflict | Aborted | optimistic locking conflict |
| Not logged in | Unauthenticated | Token missing or expired |
| No operation permission | PermissionDenied | Regular users invoking administrator interfaces |
| Rate limit triggered | ResourceExhausted | QPS limit exceeded |
| Dependency service timeout | DeadlineExceeded | Downstream service call timed out |
| Dependent service unavailable | Unavailable | Redis connection failed |
| Database exception | Internal | SQL execution error |
| Method not implemented | Unimplemented | 调用了未开发的接口 |
| Client canceled | Canceled | HTTP request disconnected |
7. Best Practices
7.1 DO (Recommended Practice)
7.1.1 Return structured status codes for all errors
go
// ✅ Good practice
return nil, status.Errorf(codes.NotFound, "user %d not found", userID)
// ❌ Bad practice
return nil, fmt.Errorf("user not found")7.1.2 Log the status code in the log
go
log.WithFields(log.Fields{
"user_id": req.UserId,
"code": st.Code().String(),
"error": st.Message(),
}).Error("failed to get user")7.1.3 Distinguish between retryable and non-retryable errors
go
func isRetryable(err error) bool {
if st, ok := status.FromError(err); ok {
switch st.Code() {
case codes.Unavailable, codes.DeadlineExceeded:
return true
}
}
return false
}7.1.4 Use predefined error variables
go
var (
ErrUserNotFound = status.Errorf(codes.NotFound, "user not found")
ErrInvalidParam = status.Errorf(codes.InvalidArgument, "invalid parameter")
)
// Return directly when used.
return nil, ErrUserNotFound7.2 DON'T (Practices to Avoid)
7.2.1 Do not disclose internal sensitive information.
go
// ❌ Do not directly expose stack traces or internal error details
return nil, status.Errorf(codes.Internal, "database connection failed: %v", err)
// ✅ Hide sensitive information externally
return nil, status.Errorf(codes.Internal, "internal server error")
// The complete error can be recorded in the log.
log.Errorf("database connection failed: %v", err)7.2.2 Do not abuse Internal Status code
All foreseeable business errors on the server side should use more precise status codes instead of returning a generic one.Internal.
7.2.3 Do not blindly retry all errors on the client side.
go
// ❌ Do not retry blindly
for i := 0; i < 3; i++ {
resp, err := client.Call(ctx, req)
if err == nil {
return resp, nil
}
// Should check whether it is retryable
}
// ✅ Only retry recoverable errorsVIII. Quick Reference Table of Common Commands
| Operation | Code example |
|---|---|
| Create Error | status.Errorf(codes.InvalidArgument, "msg: %s", detail) |
| Parse from error | st, ok := status.FromError(err) |
| Get status code | st.Code() |
| Get error information | st.Message() |
| Get additional details | st.Details() |
| Check whether it is a specific error | st.Code() == codes.NotFound |
| Determine whether it can be retried | isRetryable(err) custom function |
| Create an error with details | status.New(codes.InvalidArgument, "msg").WithDetails(detail) |
IX. Summary
| Key conclusions | Explanation |
|---|---|
| 17 standard status codes cover all scenarios | From parameter validation to server-side errors, every scenario has a corresponding status code. |
| Distinguish between client errors and server errors | Client errors (1-12, 16) require the caller to modify the request; server errors (13-15) may be retried. |
| Status code + error message + details | The combination of the three provides complete error context. |
| Logging status codes facilitates troubleshooting. | Log status codes and error messages to facilitate troubleshooting. |
| The client performs intelligent processing based on status codes. | Decide whether to retry and how to display error information based on the status code. |
gRPC's status code system provides a standardized, cross-language, and semantically clear way to express errors. In Go gRPC development, skillfully using codes bag and status The package can make your microservices' error handling more standardized, robust, and observable.
📚References