Implementing gRPC to REST Gateway in Java
gRPC is an efficient binary protocol ideal for internal microservices communication. However, many clients and partners still expect RESTful APIs over HTTP/JSON.
Instead of maintaining duplicate services, you can expose your gRPC APIs as REST endpoints. This hybrid approach gives you the best of both worlds: high-performance internal communication and familiar REST for external consumers.
In this guide, you’ll learn:
- Why you’d want a gRPC–REST gateway
- The main architectural approaches
- How to use grpc-gateway and Envoy as protocol translators
- Example configurations to get started
- Best practices for maintainable APIs
Why Bridge gRPC and REST?
✅ gRPC Advantages:
- HTTP/2 multiplexing
- Protobuf contracts
- Streaming capabilities
- Strong typing
✅ REST Advantages:
- Ubiquitous tooling
- Easy to call from browsers or mobile apps
- Human-readable JSON payloads
Common Use Cases:
- Internal services use gRPC
- External clients or legacy systems require REST
- Incrementally migrating to gRPC
Architectural Options
There are two main patterns to expose REST over gRPC:
1️⃣ grpc-gateway (Go-based)
- Generates a reverse proxy server translating HTTP/JSON into gRPC.
- Reads your
.protofiles. - Generates REST handlers automatically.
- Usually deployed alongside your Java gRPC server.
✅ Pros:
- Simpler for greenfield projects.
- Protobuf annotations drive REST mapping.
⚠ Cons:
- The gateway runs in Go, separate from your Java server.
- Requires additional deployment and build tooling.
2️⃣ Envoy Proxy
- A high-performance proxy server and service mesh.
- Uses Envoy’s
gRPC-JSON transcoderfilter to expose REST. - Runs as a standalone sidecar or gateway in Kubernetes.
✅ Pros:
- Language-agnostic.
- Powerful routing, load balancing, and observability.
- First-class Kubernetes support.
⚠ Cons:
- Steeper learning curve.
- Requires Envoy configuration skills.
Example: Defining gRPC Service
Let’s define a simple gRPC service in hello_service.proto:
syntax = "proto3";
package helloworld;
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
✅ Tip: To expose this via REST, you’ll annotate the RPC methods with HTTP bindings (see next section).
Using grpc-gateway (Go Reverse Proxy)
grpc-gateway generates a REST reverse proxy from your .proto definitions.
1️⃣ Annotate Your Service
Update hello_service.proto:
import "google/api/annotations.proto";
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse) {
option (google.api.http) = {
post: "/v1/hello"
body: "*"
};
}
}
✅ This declares that HTTP POST /v1/hello maps to SayHello.
2️⃣ Generate Code
Run protoc with grpc-gateway plugins:
protoc -I . \ -I $GOPATH/src \ -I $GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ --grpc-gateway_out=logtostderr=true:. \ hello_service.proto
This generates a Go reverse proxy server that:
- Receives REST requests.
- Marshals JSON to Protobuf.
- Invokes your Java gRPC server.
3️⃣ Deploy
- Run your Java gRPC server normally (on a port like
:50051). - Start the Go grpc-gateway server as a separate process (commonly on port
:8080).
✅ Incoming HTTP requests to the gateway are translated and proxied to the gRPC server.
Using Envoy for REST Exposure
If you prefer Envoy, you don’t need any Go code—just configuration.
1️⃣ Envoy Configuration
Here’s a minimal envoy.yaml example:
static_resources:
listeners:
- name: listener_http
address:
socket_address: { address: 0.0.0.0, port_value: 8080 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: AUTO
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: backend
domains: ["*"]
routes:
- match: { prefix: "/v1/hello" }
route: { cluster: grpc_service }
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/hello_service.pb"
services: ["helloworld.HelloService"]
print_options:
add_whitespace: true
always_print_primitive_fields: true
- name: envoy.filters.http.router
clusters:
- name: grpc_service
connect_timeout: 0.25s
type: LOGICAL_DNS
lb_policy: ROUND_ROBIN
http2_protocol_options: {}
load_assignment:
cluster_name: grpc_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: your_grpc_server
port_value: 50051
✅ Key points:
- The
proto_descriptoris a compiled descriptor set of your.protofiles. - Envoy handles HTTP/JSON to gRPC automatically.
- Routes
/v1/helloto your Java gRPC server.
2️⃣ Generate Descriptor Set
Use protoc to generate hello_service.pb:
protoc -I . --include_imports --include_source_info \ --descriptor_set_out=hello_service.pb \ hello_service.proto
3️⃣ Run Envoy
Launch Envoy with your config:
envoy -c /path/to/envoy.yaml
✅ You can now POST JSON to http://localhost:8080/v1/hello and get REST responses.
Best Practices
✅ Keep Protobuf Definitions as the Single Source of Truth
- Avoid duplicating schema in OpenAPI/Swagger.
✅ Document REST Mappings Clearly
- Use annotations consistently (
google.api.http).
✅ Monitor and Log Transcoding
- Envoy and grpc-gateway provide rich access logs.
✅ Automate Descriptor Generation
- Include
protocin your CI/CD pipeline.
✅ Secure the Gateway
- Use HTTPS, authentication, and rate limiting.
Further Reading & Tools
Conclusion
A gRPC-to-REST gateway is an elegant way to expose modern services to REST consumers without maintaining duplicate APIs. Whether you use grpc-gateway or Envoy, you get the flexibility to support clients at any maturity level—all while keeping your internal systems efficient and strongly typed.

