Skip to content

Protocols & Codecs

connect-kotlin-server implements three wire protocols from the Connect family: Connect unary (simple non-enveloped), Connect streaming (enveloped), and gRPC-Web (enveloped). Each protocol carries messages encoded in one of two codecs: proto binary or protobuf-JSON. Protocol and codec are negotiated transparently from the Content-Type header on each request.

The HTTP Content-Type header (both request and response) determines which protocol is used:

Content-TypeProtocolMessage framingUse case
application/proto, application/x-protobuf, application/protobufConnect unaryNon-envelopedUnary RPC only; lowest overhead
application/jsonConnect unaryNon-envelopedUnary RPC only; human-readable
application/connect+protoConnect streaming5-byte envelopedStreaming over Connect protocol
application/connect+jsonConnect streaming5-byte envelopedStreaming with JSON codec
application/grpc-web+protogRPC-Web5-byte envelopedBrowser clients; streaming support
application/grpc-web+jsongRPC-Web5-byte envelopedBrowser clients with JSON codec

The negotiation is case-insensitive and strips charset parameters. If no Content-Type is provided or it is unsupported, the request fails with HTTP 415 Unsupported Media Type.

Each request specifies its encoding through the codec inferred from the Content-Type (or the encoding parameter for GET requests).

Uses the standard Protocol Buffers binary wire format. Maps to application/proto and variants:

// Internally uses protobuf-java's standard serialization
message.toByteArray() // serialize
prototype.parserForType.parseFrom(data) // deserialize

Characteristics:

  • Size: compact binary encoding
  • Speed: fastest serialization/deserialization
  • Human-readable: no; requires a .proto schema to inspect

Uses the canonical Protobuf JSON representation, backed by protobuf-java-util’s JsonFormat:

// Omits insignificant whitespace; resolves google.protobuf.Any via TypeRegistry
printer.print(message).toByteArray(StandardCharsets.UTF_8)
parser.merge(jsonString, builder)

Characteristics:

  • Size: larger than binary; compresses well
  • Speed: slower than binary
  • Human-readable: yes; fully compatible with browser clients and JSON tooling
  • Any support: resolves google.protobuf.Any fields if types are registered

The message body is the serialized protobuf directly—no framing:

[message bytes]

Only supported for unary requests. Responses use the same format. Error responses are always JSON (see error handling).

Both Connect streaming and gRPC-Web use a 5-byte length-prefixed envelope format for each message:

[flags:1][length:4 big-endian][data:length]
  • flags (1 byte):
    • Bit 0 (0x01): message data is compressed
    • Bit 1 (0x02): Connect streaming end-of-stream marker
    • Bit 7 (0x80): gRPC-Web trailer frame (status + headers)
  • length (4 bytes, big-endian): byte count of data, excludes the 5-byte header
  • data (variable): the serialized message or framing payload

After the final message, Connect streaming sends a single envelope with the FLAG_CONNECT_END_STREAM flag set. The data is a JSON object with optional error and metadata fields (or empty if the call succeeded).

After the final message, gRPC-Web sends a single envelope with the FLAG_GRPC_WEB_TRAILER flag. The data is an HTTP/1 header block (key-value pairs) containing the gRPC status, message, and any error details.

RPC typeConnect unaryConnect streaminggRPC-Web
Unary
Server streaming
Client streaming / bidirectional

Unary methods work on all three protocols; the protocol is selected by Content-Type.

Server streaming requires an enveloped protocol (Connect streaming or gRPC-Web); the non-enveloped Connect protocol does not support streaming.

Client streaming and bidirectional RPC are not supported: the dispatcher handles unary and server-streaming only. (The embedded server can serve HTTP/2 cleartext when connect.server.http2 is enabled, but these RPC kinds are not yet wired.)

Unary methods marked with idempotency_level = NO_SIDE_EFFECTS in their .proto definition can be invoked via HTTP GET:

Terminal window
curl 'localhost:8080/package.Service/Method?encoding=json&message=%7B%7D'

Query parameters:

NameRequiredPurpose
encodingYesCodec: json or proto
messageYesURL-safe base64 or UTF-8 JSON string (see base64 parameter)
base64NoIf 1, decode message as URL-safe base64 instead of UTF-8 JSON
compressionNoCompression codec (e.g., gzip); only for proto-encoded messages
connectNo if requireProtocolVersion is falseVersion check: must be v1

GET is disabled by default; enable it with the connect.server.get-enabled property or ConnectServerConfig.getEnabled = true.

[!TIP] GET is useful for browser bookmarks and CDN caching of read-only operations. Since the message is passed in the query string, it is visible in logs and browser history — do not use for sensitive data.

Given a POST to POST /myapp.v1.EchoService/Echo:

Content-Type: application/connect+proto

→ Protocol: Connect streaming, Codec: proto binary, Framing: enveloped

→ Request and response messages are framed with the 5-byte envelope, suitable for streaming.


Content-Type: application/json

→ Protocol: Connect unary, Codec: protobuf-JSON, Framing: non-enveloped

→ Request and response bodies are raw JSON; only unary methods are allowed.


Content-Type: application/grpc-web+json

→ Protocol: gRPC-Web, Codec: protobuf-JSON, Framing: enveloped

→ Response includes a gRPC-Web trailer frame; suitable for browser clients.

[!NOTE] The response Content-Type always matches the request protocol (though codec preference may be influenced by client Accept headers). See the Connect spec and gRPC-Web spec for full details on header semantics.