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

> Learn Kratos Protobuf error handling. Define typed errors for gRPC and HTTP services, coupling status codes with machine-readable reasons for type-safe error propagation.

- Repository: [Kratos/kratos](https://github.com/go-kratos/kratos)
- Tags: how-to-guide
- Published: 2026-03-02

---

**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

```protobuf
// 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`](https://github.com/go-kratos/kratos/blob/main/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:

```go
// 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`](https://github.com/go-kratos/kratos/blob/main/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:

```go
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`](https://github.com/go-kratos/kratos/blob/main/transport/http/status/status.go) via a `statusConverter` type:

```go
// 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:

```protobuf
// 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:

```go
// 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:

```go
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`](https://github.com/go-kratos/kratos/blob/main/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`](https://github.com/go-kratos/kratos/blob/main/errors/errors.go) provide ergonomic error creation and extraction
- The `GRPCStatus()` method and [`transport/http/status/status.go`](https://github.com/go-kratos/kratos/blob/main/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`](https://github.com/go-kratos/kratos/blob/main/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.