Misk gRPC and Protobuf: Wire-Generated Services, JSON for Free
Series: Building Production Services with Misk — Part 8 of 24
Misk speaks gRPC as a first-class citizen, and it does so through Square’s own Wire rather than the standard gRPC-Java stack. The payoff is real: idiomatic immutable Kotlin message classes, server interfaces generated for you, and — the genuinely delightful part — an automatic JSON endpoint for every protobuf action, so you can curl a gRPC service while debugging. This post builds a misk grpc endpoint end to end.
Define the proto
A gRPC action starts with a protobuf service definition in src/main/proto. Here’s the shape behind the exemplar’s hello service:
syntax = "proto2";
package com.squareup.exemplar.protos;
option java_package = "com.squareup.exemplar.protos";
message HelloWebRequest {
required string name = 1;
optional string nick_name = 2;
repeated string greetings = 3;
}
message HelloWebResponse {
optional string greeting = 1;
optional string name = 2;
}
service HelloWebService {
rpc Hello(HelloWebRequest) returns (HelloWebResponse) {}
}
Nothing Misk-specific yet — this is plain protobuf. The service defines one unary RPC, Hello, and that’s what we’ll implement.
Generate server interfaces with Wire
Add the Wire Gradle plugin and configure it to generate Kotlin server interfaces. This is the exemplar’s actual config:
plugins {
id("com.squareup.wire")
}
wire {
protoLibrary = true // keep .proto on the runtime classpath for reflection
kotlin {
includes = listOf("com.squareup.exemplar.protos.HelloWebService")
rpcRole = "server"
rpcCallStyle = "blocking"
singleMethodServices = true
}
}
Wire generates immutable Kotlin data classes for the messages (HelloWebRequest, HelloWebResponse) and — because rpcRole = "server" and singleMethodServices = true — a one-method server interface per RPC. For Hello, that’s HelloWebServiceHelloBlockingServer. The singleMethodServices style is what lets each RPC become its own Misk action, with its own access rules and its own tests, exactly mirroring the one-action-per-endpoint convention from Part 5.
Implement a gRPC action
A gRPC action is a WebAction that also implements the generated server interface. There’s no @Get/@Post — the proto defines the method and path. The exemplar’s real implementation:
class HelloWebGrpcAction @Inject constructor() : HelloWebServiceHelloBlockingServer, WebAction {
@Unauthenticated
override fun Hello(request: HelloWebRequest): HelloWebResponse =
HelloWebResponse(
greeting = if (request.greetings.isNotEmpty()) request.greetings.joinToString(" ") else "Yo",
name = request.nick_name?.uppercase() ?: request.name.uppercase(),
)
}
It’s a normal injectable class — it can take constructor dependencies like any other action — that happens to satisfy a generated interface. Register it the usual way, with a WebActionModule:
install(WebActionModule.create<HelloWebGrpcAction>())
That’s a working gRPC endpoint. Same registration, same access annotations, same DI as an HTTP action — gRPC is just another protocol riding the action model.
A JSON endpoint, for free
Here’s the feature that earns its own headline. Misk automatically creates a JSON variant of every protobuf-based action. The gRPC action above also answers a plain HTTP POST with Content-Type: application/json, at the same path, with the request and response as JSON versions of the protobuf messages — no gRPC framing, no HTTP/2 required.
This is enormously practical. You can curl a gRPC service from a terminal, hit it from a browser-based tool, or write a quick integration test without standing up a gRPC client. The strongly-typed protobuf contract stays the source of truth; the JSON endpoint is a debugging and compatibility convenience that costs you nothing.
Unframed protobuf, and the three-protocol action
There’s a second opt-in. Add @EnableUnframedRequests to a unary RPC and it also accepts raw protobuf over a plain HTTP POST (application/x-protobuf) without gRPC’s HTTP/2 framing — useful when migrating an existing protobuf-over-HTTP API to gRPC. With both features, a single action speaks three protocols at once:
| Protocol | Content-Type | How |
|---|---|---|
| gRPC | application/grpc | standard, HTTP/2 framed |
| JSON POST | application/json | automatic, always on |
| Protobuf POST | application/x-protobuf | opt in with @EnableUnframedRequests |
If you’d rather write a dedicated protobuf-over-HTTP endpoint explicitly, that’s just a normal @Post action declaring protobuf content types — the exemplar’s HelloWebProtoAction does exactly this, taking a @RequestBody HelloWebRequest and returning Response<HelloWebResponse> with @RequestContentType(MediaTypes.APPLICATION_PROTOBUF). But for a gRPC service, @EnableUnframedRequests gets you there without the extra class.
Blocking vs. suspending
The exemplar uses rpcCallStyle = "blocking", which generates a plain fun Hello(...). Switch to rpcCallStyle = "suspending" and Wire generates a suspend fun instead, so you can implement the handler with coroutines and call suspending collaborators directly. Pick blocking for straightforward request/response handlers, suspending when your business logic is already coroutine-based. (Misk’s coroutine support is its own topic; the docs’ coroutines.md has the details.)
Reflection
protoLibrary = true in the Wire config keeps the .proto descriptors on the runtime classpath, which is what gRPC server reflection needs. With reflection enabled (via misk-grpc-reflect), tools like grpcurl can discover your service’s methods and message shapes at runtime without you shipping them the proto files — the standard way to poke at a gRPC service in staging.
Production notes & gotchas
singleMethodServices = trueis the Misk-friendly style. It generates one server interface per RPC, so each becomes its own action with its own access annotation and test. Multi-method services fight the one-action-per-endpoint model.- The JSON endpoint is always on — secure it like any endpoint. Auto-JSON means your gRPC action is also reachable over plain HTTP POST. Its access annotation (
@Authenticated/@Unauthenticated) applies to all variants, so don’t assume “it’s gRPC, it’s internal.” protoLibrary = trueis required for reflection. Forget it andgrpcurl-style discovery won’t work, even though the service runs fine. It’s cheap; leave it on.- Match
rpcCallStyleto your code. A blocking handler that blocks a thread on I/O under load is a throughput problem; if your logic is coroutine-based, generate suspending servers rather than bridging withrunBlocking. - Proto compatibility is your contract. Wire generates from the proto, so changing field numbers or types breaks clients the usual protobuf way. Treat the
.protoas the versioned interface it is.
What’s next
Your endpoints serve HTTP, JSON, and gRPC. Now: who’s allowed to call them? In Part 9: Authentication & Access Control we’ll cover @Authenticated, the MiskCaller model, custom access annotations, the AccessInterceptor that enforces it all, and folding in audit logging.
Target keywords: misk grpc, misk protobuf wire, misk grpc json.
Comments