Envoy xDS vs Tyk for protocol translation
When a browser client speaks JSON over HTTP/1.1 and the backend fleet speaks Protobuf over gRPC, something in the middle has to translate — and where that translation happens determines your latency floor, your schema-drift blast radius, and how much control-plane machinery you have to operate. This page compares two very different answers: Envoy 1.32+, whose grpc_json_transcoder filter converts REST calls into gRPC inside its C++ data plane under xDS control, and Tyk 5.x, whose GraphQL/REST surface and gRPC plugins bridge protocols at the API-management layer. The choice is rarely about raw speed; it is about whether translation belongs in a proxy driven by a proto descriptor set or in a gateway product driven by JSON API definitions.
Prerequisite concepts
This comparison assumes you have read the protocol translation patterns overview — specifically how transcoding fidelity, content-type negotiation, and trailer handling shape a translation design — and understand where a gateway sits in the broader API gateway fundamentals and architecture. If your workload is REST ingress over a Protobuf backend at high volume, read handling gRPC-to-REST translation at scale first, because the descriptor-management and streaming-limit issues raised there are the exact constraints that separate these two gateways.
Where translation happens: two architectures
The critical difference is the layer at which the protocol boundary is crossed. Envoy performs the conversion inline in the data plane: the proto descriptor set is shipped to the proxy as configuration, and every request body is transcoded in the same reactor loop that proxies it. Tyk keeps the proxy transport-transparent and pushes any real transcoding to a plugin or an upstream sidecar, translating at the management layer rather than in the byte stream.
That architectural split explains almost every downstream trade-off: Envoy owns the message bytes and pays a control-plane tax; Tyk owns the API product surface and pays a per-hop plugin tax.
Envoy 1.32+: xDS-driven in-data-plane transcoding
Envoy compiles a proto_descriptor (a FileDescriptorSet produced by protoc --descriptor_set_out) into the HTTP filter chain. When a REST request matches an HTTP rule defined by the google.api.http annotation, Envoy synthesizes the corresponding Protobuf message, opens an HTTP/2 gRPC stream to the upstream, and transcodes the gRPC response back to JSON — all inside the reactor, with no plugin process and no extra network hop.
# Envoy 1.32+ — grpc_json_transcoder filter in the HTTP connection manager
http_filters:
- name: envoy.filters.http.grpc_json_transcoder
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder
proto_descriptor: "/etc/envoy/proto/orders.pb" # protoc --descriptor_set_out
services:
- "orders.v1.OrderService"
print_options:
add_whitespace: false
always_print_primitive_fields: true # emit zero-valued fields, not omit
preserve_proto_field_names: true # keep snake_case, don't camelCase
convert_grpc_status: true # map grpc-status trailer to HTTP status
request_validation_options:
reject_unknown_method: true
reject_unknown_query_parameters: true
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
The upstream cluster must advertise HTTP/2 so the transcoded call is a real gRPC stream:
# Envoy 1.32+ — upstream cluster must speak HTTP/2 for gRPC
clusters:
- name: orders_grpc
connect_timeout: 1s
type: STRICT_DNS
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options:
max_concurrent_streams: 100
load_assignment:
cluster_name: orders_grpc
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address: { address: orders.internal, port_value: 9000 }
The fidelity knobs matter. preserve_proto_field_names: true keeps order_id instead of camelCasing it to orderId; always_print_primitive_fields: true decides whether a zero-valued int appears in the JSON or is dropped — a frequent source of client-side breakage. convert_grpc_status: true translates a grpc-status trailer such as NOT_FOUND into HTTP 404, so REST clients see conventional status codes rather than always-200 gRPC semantics. Because all of this is configuration, the whole filter — including a rebuilt descriptor after a .proto change — is delivered by xDS and hot-reloaded without dropping active connections.
Tyk 5.x: transport-transparent proxy with plugin translation
Tyk approaches the problem from the API-management side. Its gateway proxies HTTP/2 and native gRPC transparently, and it can expose a Protobuf backend through a GraphQL or REST facade defined in an API definition. What it does not do is transcode message bodies in-process the way Envoy does — real REST-to-gRPC body conversion is delegated to a gRPC plugin (a coprocess gRPC server) or an upstream sidecar.
{
"name": "orders-grpc-facade",
"api_id": "orders_grpc_01",
"use_keyless": false,
"protocol": "h2c",
"proxy": {
"listen_path": "/v1/orders/",
"target_url": "grpc://orders.internal:9000",
"strip_listen_path": true
},
"custom_middleware": {
"driver": "grpc",
"pre": [
{ "name": "RestToGrpcTranscode", "path": "" }
],
"post": [
{ "name": "GrpcToRestTranscode", "path": "" }
]
}
}
The plugin server is a standalone process that Tyk calls over gRPC for each hooked request:
# Tyk 5.x — gateway config (tyk.conf) pointing at the coprocess plugin server
coprocess_options:
enable_coprocess: true
coprocess_grpc_server: "tcp://plugin-host.internal:5555"
grpc_recv_max_size: 33554432 # 32 MiB — must exceed largest transcoded body
grpc_send_max_size: 33554432
slave_options:
use_rpc: false
Each hooked request crosses the process boundary to the plugin server and back — that is the operational cost Tyk trades for keeping the proxy protocol-agnostic and the translation logic in your own Go, Python, or Ruby code. The upside is that the same protocol translation plugin can carry arbitrary business logic (field remapping, enrichment, auth-aware shaping) that Envoy’s declarative transcoder cannot express. The downside is a second process to deploy, scale, and monitor, plus a hard message-size ceiling (grpc_recv_max_size) that silently truncates or rejects oversized payloads if left at defaults.
Streaming fidelity
Streaming is where transcoding gets sharp edges. Envoy’s grpc_json_transcoder handles unary and server-streaming methods: a server stream is emitted as a JSON array (or newline-delimited JSON with the right Accept), so a REST client can consume a stream of messages. It does not transcode client-streaming or bidirectional-streaming methods into REST — those must stay native gRPC. Tyk, by contrast, proxies native gRPC streams end-to-end untouched, which means a non-gRPC client cannot consume them at all without your plugin converting frame-by-frame. For any bidirectional method you intend to expose to browsers, neither gateway is a transcoder; bridge it with WebSocket or SSE at the application layer instead.
Decision matrix
| Criterion | Envoy 1.32+ (xDS) | Tyk 5.x |
|---|---|---|
| Translation layer | In data plane, C++ core | Plugin process / upstream sidecar |
| Config source of truth | Proto descriptor set via xDS | JSON API definition via Dashboard/Redis |
| REST↔gRPC body transcoding | Native (grpc_json_transcoder) |
Custom gRPC plugin required |
| Server streaming to REST | Supported (JSON array / NDJSON) | Only via custom plugin logic |
| Client / bidi streaming to REST | Not transcoded | Not transcoded (native passthrough) |
| Fidelity control | preserve_proto_field_names, convert_grpc_status |
Whatever you code in the plugin |
| Per-request overhead | Sub-ms, in-process | Plugin IPC hop (latency + failure surface) |
| Operational prerequisite | xDS server + descriptor pipeline | Dashboard/Redis + plugin runtime |
| Best fit | High-volume REST-over-gRPC, mesh teams | Product APIs needing a portal + bespoke logic |
Default to Envoy when translation is high-volume, schema-stable, and expressible declaratively, and when you already operate an xDS control plane. Default to Tyk when the translation carries meaningful business logic, when you want a developer portal and management UI out of the box, or when translation is a minority of traffic and standing up an xDS pipeline is not justified.
Gotchas and failure signals
Envoy — stale descriptor set. The proto_descriptor is a compiled artifact, not the .proto source. Change a message and forget to regenerate orders.pb, and Envoy transcodes against the old schema — new fields silently vanish. Version the descriptor and push it through the same xDS pipeline as the rest of the config; alert on descriptor hash mismatch across the fleet.
Envoy — camelCase surprise. Without preserve_proto_field_names: true, Envoy camelCases every field name, so order_id becomes orderId in the JSON. Clients written against the .proto snake_case names break. Decide the casing policy once and pin it.
Envoy — HTTP/1 upstream misconfig. If the upstream cluster is not configured for http2_protocol_options, the transcoded gRPC call fails with an obscure protocol error rather than a clear message. gRPC upstreams must be HTTP/2.
Tyk — plugin size ceiling. grpc_recv_max_size / grpc_send_max_size default well below what a large transcoded payload needs; oversized messages fail at the coprocess boundary, not at the client. Size these to your largest realistic body plus headroom.
Tyk — plugin as a hidden SPOF. Every hooked request depends on the plugin server. If it is unhealthy, transcoded routes fail even though the proxy is up. Run the plugin server with its own health checks and horizontal replicas, and treat its latency as part of the gateway’s P99 budget.
Both — trailer-only gRPC errors. A gRPC method that returns an error in trailers with no body must map to a sensible HTTP status. Envoy needs convert_grpc_status: true; in Tyk your plugin must inspect the grpc-status trailer explicitly, or clients receive a 200 wrapping an error.
Validation
- Proto descriptor set (Envoy) is regenerated in CI on every
.protochange and versioned alongside xDS config. -
preserve_proto_field_namesandalways_print_primitive_fieldsare set to match the documented client contract. -
convert_grpc_status: true(Envoy) or explicit trailer inspection (Tyk) maps gRPC error codes to HTTP status. - gRPC upstream clusters advertise HTTP/2 (
http2_protocol_options). - Tyk
grpc_recv_max_size/grpc_send_max_sizeexceed the largest expected transcoded payload. - Tyk plugin server has independent health checks, replicas, and a latency budget folded into gateway P99.
- Streaming methods are classified: server-streaming transcoded, client/bidi kept native or bridged.
- End-to-end check with
grpcurl(native) andcurl(transcoded) confirms both surfaces return identical data.
FAQ
Does Tyk transcode gRPC to JSON natively like Envoy’s grpc_json_transcoder?
No. Tyk 5.x proxies HTTP/2 and gRPC frames transparently and can expose a gRPC upstream through its GraphQL and REST layers, but it does not perform protobuf-to-JSON transcoding inside the proxy the way Envoy’s grpc_json_transcoder filter does. To get REST-to-gRPC transcoding with Tyk you attach a gRPC plugin or place a transcoding sidecar upstream. Envoy compiles the proto descriptor set directly into the filter and converts message bodies in its C++ core with no extra hop.
How does the control-plane model differ between Envoy xDS and Tyk?
Envoy is configured by an xDS control plane that streams Listener, Route, and Cluster resources over gRPC; the transcoder’s proto descriptor set is delivered as part of that config and hot-reloaded without dropping connections. Tyk uses its Dashboard and Gateway API as the control plane, distributing JSON API definitions through Redis and a synchronization API. Envoy’s model favors teams already running a service mesh; Tyk’s favors teams that want a batteries-included management UI and developer portal.
Which handles gRPC streaming better for protocol translation?
Envoy’s grpc_json_transcoder supports unary and server-streaming methods, emitting a JSON array or newline-delimited JSON for server streams, but it does not transcode client-streaming or bidirectional-streaming methods to REST. Tyk proxies native gRPC streams end-to-end without translating them, so a browser or REST client cannot consume them directly. For true bidirectional streaming exposed as REST, neither gateway transcodes it; keep those methods on native gRPC or use WebSocket/SSE bridges.
What is the operational cost difference between the two approaches?
Envoy adds control-plane cost: you must maintain an xDS server, compile and version a proto descriptor set, and validate typed_config against the running Envoy version. Tyk adds runtime cost per plugin hop: a gRPC plugin invocation crosses a process boundary and adds latency and a failure surface. Envoy’s transcoding is cheaper per request once configured; Tyk is cheaper to stand up if you already run its Dashboard and only need occasional translation.
Parent: Protocol Translation Patterns
Related
- Handling gRPC-to-REST Translation at Scale — descriptor management, streaming limits, and throughput ceilings for transcoding fleets.
- API Gateway Fundamentals & Architecture — where protocol translation sits in the overall data-plane and control-plane model.