How to Implement Custom Codec Serializers in Kratos for Non-Standard Formats

You can implement custom codec serializers in Kratos by creating a type that satisfies the encoding.Codec interface and registering it with encoding.RegisterCodec, enabling automatic content negotiation for any non-standard media type such as application/vnd.myapp+protobuf or application/custom.

The go-kratos/kratos framework abstracts serialization behind a pluggable codec system defined in encoding/encoding.go. By implementing custom codec serializers, you can transparently handle proprietary binary formats, specialized JSON variants, or legacy wire protocols without modifying core transport logic in the HTTP or gRPC layers.

Understanding the Codec Interface and Registry

The foundation of Kratos serialization is the encoding.Codec interface. This contract requires three methods: Marshal(v any) ([]byte, error) for serialization, Unmarshal(data []byte, v any) error for deserialization, and Name() string which returns the content subtype (e.g., "json", "protobuf", "custom").

The global registry in encoding/encoding.go stores codec instances in a map keyed by the lower-cased result of Name(). The RegisterCodec function validates the codec and adds it to registeredCodecs, making it available to all transports.

How Kratos Looks Up Custom Codecs

Kratos transports automatically select codecs based on HTTP headers or gRPC content types. The lookup mechanism extracts the subtype from headers and queries the registry.

Request Decoding via CodecForRequest

In transport/http/codec.go, the CodecForRequest function scans the Content-Type header, extracts the subtype using httputil.ContentSubtype, and calls encoding.GetCodec to obtain the matching codec. If none is found, it falls back to the built-in JSON codec.

// transport/http/codec.go
func CodecForRequest(r *http.Request, name string) (encoding.Codec, bool) {
    for _, accept := range r.Header[name] {
        codec := encoding.GetCodec(httputil.ContentSubtype(accept))
        if codec != nil {
            return codec, true
        }
    }
    return encoding.GetCodec("json"), false
}

Response Encoding via CodecForResponse

The CodecForResponse function performs the same lookup against the Content-Type or Accept headers to determine the appropriate encoder for responses.

// transport/http/codec.go
func CodecForResponse(r *http.Response) encoding.Codec {
    codec := encoding.GetCodec(httputil.ContentSubtype(r.Header.Get("Content-Type")))
    if codec != nil {
        return codec
    }
    return encoding.GetCodec("json")
}

Codec Registration Mechanism

The central registry implementation in encoding/encoding.go handles the mapping:

// encoding/encoding.go
var registeredCodecs = make(map[string]Codec)

func RegisterCodec(codec Codec) {
    if codec == nil {
        panic("cannot register a nil Codec")
    }
    if codec.Name() == "" {
        panic("cannot register Codec with empty string result for Name()")
    }
    contentSubtype := strings.ToLower(codec.Name())
    registeredCodecs[contentSubtype] = codec
}

Step-by-Step: Creating a Custom Codec Serializer

Follow these steps to integrate a non-standard format into your Kratos application:

  1. Implement the Codec Interface – Create a type satisfying encoding.Codec with Marshal, Unmarshal, and Name methods.
  2. Register the Codec – Call encoding.RegisterCodec in an init function or during application bootstrap.
  3. Configure Content Negotiation – Set the appropriate Content-Type or Accept headers to trigger automatic codec selection.
  4. Extend to gRPC (Optional) – For gRPC transports, additionally register with google.golang.org/grpc/encoding.

Complete Implementation Example

Below is a minimal custom codec that handles a fictional application/custom format.

// custom_codec.go
package mycodec

import (
    "errors"
    "github.com/go-kratos/kratos/v2/encoding"
)

// customCodec implements the encoding.Codec interface.
type customCodec struct{}

// Marshal converts a Go value into the custom wire format.
func (customCodec) Marshal(v any) ([]byte, error) {
    s, ok := v.(string)
    if !ok {
        return nil, errors.New("customCodec only supports string payloads")
    }
    // prepend a marker to identify the format
    return []byte("CUST:" + s), nil
}

// Unmarshal parses the custom wire format back into a Go value.
func (customCodec) Unmarshal(data []byte, v any) error {
    ptr, ok := v.(*string)
    if !ok {
        return errors.New("customCodec only supports *string destination")
    }
    // strip the marker
    if len(data) < 5 || string(data[:5]) != "CUST:" {
        return errors.New("invalid custom payload")
    }
    *ptr = string(data[5:])
    return nil
}

// Name returns the media subtype used in HTTP headers.
func (customCodec) Name() string { return "custom" }

// init registers the codec when the package is imported.
func init() {
    encoding.RegisterCodec(customCodec{})
}

Using the Codec in HTTP Handlers

Once registered, the codec activates automatically based on headers:

func MyHandler(w http.ResponseWriter, r *http.Request) {
    // Expect the client to send Content-Type: application/custom
    var payload string
    if err := http.DefaultRequestDecoder(r, &payload); err != nil {
        http.DefaultErrorEncoder(w, r, err)
        return
    }

    // Echo back using the same format (Accept header influences response)
    resp := "received:" + payload
    if err := http.DefaultResponseEncoder(w, r, resp); err != nil {
        http.DefaultErrorEncoder(w, r, err)
    }
}

When the client sends:

POST /my-endpoint HTTP/1.1
Content-Type: application/custom
Accept: application/custom

CUST:hello

the server decodes via customCodec.Unmarshal and encodes replies via customCodec.Marshal.

Registering for gRPC Transport

For gRPC, register the codec with both Kratos and the standard gRPC encoding package:

// grpc_custom.go
package mygrpc

import (
    "google.golang.org/grpc/encoding"
    kratosencoding "github.com/go-kratos/kratos/v2/encoding"
    "myproject/mycodec"
)

// init registers the codec for both transports.
func init() {
    // Kratos registration (already performed by mycodec init)
    // Register with gRPC using the same name.
    encoding.RegisterCodec(&grpcCodec{})
}

type grpcCodec struct{}

func (grpcCodec) Name() string { return mycodec.customCodec{}.Name() }

func (grpcCodec) Marshal(v any) ([]byte, error) {
    return mycodec.customCodec{}.Marshal(v)
}

func (grpcCodec) Unmarshal(data []byte, v any) error {
    return mycodec.customCodec{}.Unmarshal(data, v)
}

Now gRPC services can exchange the custom format via the same subtype (custom).

Key Source Files in go-kratos/kratos

File Purpose
[encoding/encoding.go](https://github.com/go-kratos/kratos/blob/main/encoding/encoding.go) Defines Codec interface and the global registry (RegisterCodec, GetCodec).
[transport/http/codec.go](https://github.com/go-kratos/kratos/blob/main/transport/http/codec.go) Shows how HTTP transports query the registry based on Content-Type / Accept.
[transport/http/codec_test.go](https://github.com/go-kratos/kratos/blob/main/transport/http/codec_test.go) Demonstrates registration of a mock codec and the effect on encoding/decoding.
[transport/grpc/codec.go](https://github.com/go-kratos/kratos/blob/main/transport/grpc/codec.go) Shows integration of Kratos codecs with the gRPC encoding subsystem.

Summary

  • Custom codec serializers in Kratos implement the encoding.Codec interface with Marshal, Unmarshal, and Name methods.
  • Register implementations via encoding.RegisterCodec in encoding/encoding.go to make them available globally.
  • HTTP transports in transport/http/codec.go automatically select codecs by inspecting Content-Type and Accept headers using httputil.ContentSubtype.
  • For gRPC support, additionally register with google.golang.org/grpc/encoding to bridge Kratos codecs with the gRPC transport.
  • This architecture enables transparent handling of non-standard formats without modifying core framework code.

Frequently Asked Questions

What is the Codec interface in Kratos?

The Codec interface is defined in encoding/encoding.go and requires three methods: Marshal(v any) ([]byte, error) for serialization, Unmarshal(data []byte, v any) error for deserialization, and Name() string which returns the content subtype (e.g., "json", "custom"). All custom serializers must satisfy this contract to integrate with Kratos transports.

How does Kratos determine which codec to use for an HTTP request?

Kratos determines the codec by examining the Content-Type header for requests and the Accept header for responses. In transport/http/codec.go, the CodecForRequest function extracts the subtype using httputil.ContentSubtype and queries the registry via encoding.GetCodec. If no registered codec matches, it falls back to the built-in JSON codec.

Can the same custom codec be used for both HTTP and gRPC transports?

Yes, but gRPC requires additional registration. While Kratos uses its own registry in encoding/encoding.go, the gRPC transport relies on the standard google.golang.org/grpc/encoding package. You must create a wrapper that implements both interfaces and register it with both encoding.RegisterCodec (for Kratos) and grpc.encoding.RegisterCodec (for gRPC) to ensure the custom format works across all transports.

What happens if no codec is found for the requested content type?

If encoding.GetCodec returns nil because no registered codec matches the extracted subtype, the HTTP transport in transport/http/codec.go falls back to the built-in JSON codec. This ensures the server can always respond, but it may result in encoding errors if the client expects a different format. Always verify that your custom codec is properly registered and the content type headers are correctly set to avoid unexpected fallback behavior.

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 →