Kratos Protobuf Error Handling: Defining Typed Errors for gRPC and HTTP Services

Kratos implements a protobuf-based error model that couples HTTP/gRPC status codes with machine-readable reasons and human-readable messages, enabling type-safe error propagation across transport layers.

The go-kratos/kratos framework standardizes service error handling through Protocol Buffer definitions. By extending protobuf enum options and providing a canonical Status message, Kratos allows developers to define domain-specific errors that automatically serialize for both HTTP and gRPC transports while preserving structured metadata.

The Protobuf Foundation

Kratos extends the standard protobuf descriptor to support transport-agnostic error definitions. In third_party/errors/errors.proto, the framework defines two custom options that attach HTTP/gRPC codes directly to enum definitions:

  • default_code (extension 1108) – Assigns a default status code to an entire enum type
  • code (extension 1109) – Overrides the code for specific enum values
// third_party/errors/errors.proto
extend google.protobuf.EnumOptions {
  int32 default_code = 1108;
}

extend google.protobuf.EnumValueOptions {
  int32 code = 1109;
}

These extensions allow API designers to co-locate error semantics with their protocol definitions, ensuring that error codes remain consistent across service boundaries.

Core Error Types

The generated Go code in errors/errors.pb.go implements the underlying data structures. The Status message carries four essential fields, while the Error struct wraps Status to implement Go's native error interface:

// errors/errors.pb.go
type Status struct {
    Code     int32             `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
    Reason   string            `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"`
    Message  string            `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
    Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty"`
}

type Error struct {
    Status
    cause error
}

The Reason field serves as a machine-readable identifier (e.g., INVALID_ARGUMENT), while Message provides human-readable context. The Metadata map supports arbitrary key-value pairs for debugging context.

Error Construction and Extraction

The errors/errors.go file provides constructors that instantiate the Error struct with proper initialization:

  • New(code int, reason, message string) – Creates a base error from explicit values
  • Newf(code int, reason, format string, a ...any) – Supports formatted messages
  • Errorf(code int, reason, format string, a ...any) – Alias for Newf
  • FromError(err error) *Error – Unwraps any error (including gRPC status errors) back into a Kratos *Error

Helper functions Code(err error) and Reason(err error) extract specific fields without manual type assertion, returning zero values for non-Kratos errors.

Transport-Aware Error Conversion

Kratos handles automatic serialization through the (*Error).GRPCStatus() method, which converts the error into a grpc/status.Status containing an errdetails.ErrorInfo payload:

func (e *Error) GRPCStatus() *status.Status {
    s, _ := status.New(httpstatus.ToGRPCCode(int(e.Code)), e.Message).
        WithDetails(&errdetails.ErrorInfo{
            Reason:   e.Reason,
            Metadata: e.Metadata,
        })
    return s
}

The HTTP-to-gRPC code mapping is implemented in transport/http/status/status.go via a statusConverter type:

// transport/http/status/status.go
func (c statusConverter) ToGRPCCode(code int) codes.Code { … }
func (c statusConverter) FromGRPCCode(code codes.Code) int { … }

This bidirectional conversion ensures that errors maintain semantic meaning when services communicate across different transport protocols.

Implementing Protobuf Errors in Services

Defining Domain Error Enums

Service-specific errors should be defined in protobuf using the custom options. The following example defines a HelloError enum with a default 400 status code and specific overrides:

// api/v1/error.proto
syntax = "proto3";
package api.v1;

import "errors/errors.proto";

enum HelloError {
  option (errors.default_code) = 400;

  INVALID_NAME = 0 [(errors.code) = 400];
  UNAUTHORIZED = 1 [(errors.code) = 401];
}

Returning Typed Errors from Service Methods

Service implementations construct errors using the generated enum values and reason strings. The errors.New function accepts the int32 value cast from the generated enum:

// internal/service/greeter.go
import (
    "github.com/go-kratos/kratos/v2/errors"
    pb "github.com/example/api/v1"
)

func (s *greeterServer) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
    if req.Name == "" {
        return nil, errors.New(int32(pb.HelloError_INVALID_NAME), "INVALID_NAME", "name is required")
    }
    return &pb.HelloReply{Message: "Hello " + req.Name}, nil
}

Client-Side Error Inspection

Clients receive errors through the generated client stubs. Use errors.FromError to recover the original *Error and inspect its fields:

resp, err := greeterClient.SayHello(context.Background(), &pb.HelloRequest{Name: ""})
if err != nil {
    krerr := errors.FromError(err)
    code := errors.Code(err)     // → 400
    reason := errors.Reason(err) // → "INVALID_NAME"
    
    fmt.Printf("error %d (%s): %v\n", code, reason, krerr)
}

The FromError function handles wrapped errors and gRPC status conversions, making it safe to use regardless of transport middleware.

Summary

  • Kratos extends protobuf enums with default_code (extension 1108) and code (extension 1109) options to define HTTP/gRPC status codes at the API contract level
  • The Status message and Error struct in errors/errors.pb.go provide the concrete Go representation with Code, Reason, Message, and Metadata fields
  • Constructor functions New, Newf, and FromError in errors/errors.go provide ergonomic error creation and extraction
  • The GRPCStatus() method and transport/http/status/status.go converter ensure seamless translation between HTTP and gRPC transports
  • Service handlers return *Error values that automatically serialize across transport boundaries while preserving type-safe error inspection on the client

Frequently Asked Questions

How do I define a custom error code in Kratos protobuf?

Define an enum in your protobuf file and import errors/errors.proto. Use the (errors.default_code) option on the enum for a default HTTP/gRPC code, and override specific values with (errors.code). The protoc-gen-go compiler will generate typed constants you can cast to int32 when calling errors.New.

What is the difference between Kratos Error and standard Go error?

The Kratos *Error type implements the standard error interface but adds transport-aware serialization through GRPCStatus(), structured Reason codes for programmatic error handling, and Metadata for debugging context. Standard Go errors lack these transport and metadata capabilities required for cross-service communication.

How does Kratos handle error conversion between HTTP and gRPC?

Kratos uses the statusConverter in transport/http/status/status.go to map HTTP status codes to gRPC codes and vice versa. When an error crosses transport boundaries, the GRPCStatus() method packages the Reason and Metadata into errdetails.ErrorInfo, while HTTP encoders use the Code field directly.

Can I extract metadata from a Kratos error returned over gRPC?

Yes. When a Kratos error propagates over gRPC, the GRPCStatus() method attaches the Metadata map to the errdetails.ErrorInfo details. On the client side, errors.FromError reconstructs the original *Error with its Metadata field intact, allowing access to custom debugging information without parsing message strings.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →