Skip to content

A Complete Guide to gRPC Status Codes ​

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? ​

SceneExplanation
Cross-language unificationgRPC supports multiple languages, and standardized status codes allow services in different languages to understand each other's errors.
Clear semanticsEach status code has clear semantics, and the caller can implement corresponding processing logic based on the status code.
Debug-friendlyStandardized error codes make log tracing and problem localization more efficient.
Client retry strategyThe 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 codeNumerical valuemeaningApplicable scenarios
OK0SuccessRequest processed successfully
Canceled1The operation was canceledThe client actively cancels the request.
Unknown2Unknown errorUnclassifiable error
InvalidArgument3Invalid parameterRequest parameter validation failed
DeadlineExceeded4timeoutThe request exceeds the set deadline.
NotFound5Resource does not existThe requested resource was not found.
AlreadyExists6Resource already existsA conflict occurred while creating the resource
PermissionDenied7Insufficient permissionsInsufficient permissions to perform the operation
ResourceExhausted8Resource depletionInsufficient quota, rate limiting triggered
FailedPrecondition9Precondition failedThe prerequisites for the operation are not met.
Aborted10Operation abortedConcurrency conflict (similar to HTTP 409)
OutOfRange11Out of rangeThe requested value is out of the valid range.
Unimplemented12Not implementedMethod not implemented or not supported.
Internal13Internal errorInternal server error
Unavailable14Service unavailableThe service is temporarily unable to process the request.
DataLoss15Data LossUnrecoverable data corruption or loss
Unauthenticated16UnverifiedThe 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 codeDescription
OKThe 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 codeCorresponding HTTP semanticsDescription
Canceled499(Client Closed Request)Client actively cancels
InvalidArgument400(Bad Request)Parameter error
NotFound404(Not Found)Resource does not exist
AlreadyExists409(Conflict)Resource already exists
PermissionDenied403(Forbidden)Insufficient permissions
ResourceExhausted429(Too Many Requests)Quota/rate limiting
FailedPrecondition412(Precondition Failed)Precondition failed
Aborted409(Conflict)Concurrency conflict
OutOfRange400(Bad Request)Parameter out of range
Unauthenticated401(Unauthorized)Unverified

3.3 Server-side Error Category (5xx Semantics) ​

Caused by a server-side issue; the client can consider retrying:

Status codeCorresponding HTTP semanticsDescriptionWhether retry is possible
DeadlineExceeded504(Gateway Timeout)Timeout✅ Retry after adjusting the deadline
Unimplemented501(Not Implemented)Method not implemented❌ Do not retry
Internal500(Internal Server Error)Internal server error⚠️ Retry with caution
Unavailable503(Service Unavailable)Service unavailable✅ Retryable (exponential backoff)
DataLoss500(Internal Server Error)Data loss❌ Unrecoverable, manual intervention required

3.4 Unknown class ​

Status codeExplanation
UnknownUse 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 ​

SceneRecommended status codeExample
Parameter format errorInvalidArgumentEmail format is incorrect
Parameter value out of rangeOutOfRangeThe page number is negative.
Resource does not existNotFoundFailed to query user by ID
Create resource conflictAlreadyExistsThe username is already registered.
Concurrent update conflictAbortedoptimistic locking conflict
Not logged inUnauthenticatedToken missing or expired
No operation permissionPermissionDeniedRegular users invoking administrator interfaces
Rate limit triggeredResourceExhaustedQPS limit exceeded
Dependency service timeoutDeadlineExceededDownstream service call timed out
Dependent service unavailableUnavailableRedis connection failed
Database exceptionInternalSQL execution error
Method not implementedUnimplemented调用了未开发的接口
Client canceledCanceledHTTP request disconnected

7. Best Practices ​

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, ErrUserNotFound

7.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 errors

VIII. Quick Reference Table of Common Commands ​

OperationCode example
Create Errorstatus.Errorf(codes.InvalidArgument, "msg: %s", detail)
Parse from errorst, ok := status.FromError(err)
Get status codest.Code()
Get error informationst.Message()
Get additional detailsst.Details()
Check whether it is a specific errorst.Code() == codes.NotFound
Determine whether it can be retriedisRetryable(err) custom function
Create an error with detailsstatus.New(codes.InvalidArgument, "msg").WithDetails(detail)

IX. Summary ​

Key conclusionsExplanation
17 standard status codes cover all scenariosFrom parameter validation to server-side errors, every scenario has a corresponding status code.
Distinguish between client errors and server errorsClient errors (1-12, 16) require the caller to modify the request; server errors (13-15) may be retried.
Status code + error message + detailsThe 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

Last updated on11/07/2026, 02:16 PM
@fishfinal | @fishfinals | [email protected]