Mocking gRPC Services
MockServer supports mocking gRPC services by transparently converting gRPC requests (protobuf over HTTP/2) into JSON-over-HTTP requests internally. This allows the standard expectation matching engine to handle gRPC requests using the same JSON format used for HTTP mocking.
How gRPC Mocking Works
When MockServer receives a gRPC request:
- The protobuf binary body is decoded to JSON using the loaded proto descriptors
- MockServer adds metadata headers to the converted request:
- x-grpc-service — the fully-qualified gRPC service name
- x-grpc-method — the RPC method name
- x-grpc-original-content-type — the original gRPC content type
- The converted JSON request is matched against active expectations
- The JSON response is encoded back to protobuf binary and returned as a gRPC response
This means you can set up gRPC expectations using the same JSON format and client APIs as HTTP mocking — no special gRPC client tooling is needed.
Loading Proto Descriptors
MockServer needs proto descriptors to convert between protobuf binary and JSON. There are three ways to load them:
1. Pre-compiled Descriptor Files
Compile your .proto files to descriptor sets and point MockServer at the directory:
protoc --descriptor_set_out=service.dsc --include_imports service.proto
Then configure MockServer:
-Dmockserver.grpcDescriptorDirectory="/path/to/descriptors"
Or via environment variable:
MOCKSERVER_GRPC_DESCRIPTOR_DIRECTORY=/path/to/descriptors
2. Proto Source Files (Auto-compiled)
Point MockServer at a directory of .proto source files and they will be compiled at startup using protoc:
-Dmockserver.grpcProtoDirectory="/path/to/protos"
This requires protoc to be available on the system PATH. If protoc is installed elsewhere, configure its path:
-Dmockserver.grpcProtocPath="/usr/local/bin/protoc"
3. REST API Upload
Upload compiled descriptors at runtime via the REST API:
curl -v -X PUT "http://localhost:1080/mockserver/grpc/descriptors" \
--data-binary @service.dsc
Docker
When running MockServer in Docker, mount your proto files or descriptors into the container:
docker run -d --rm \
-p 1080:1080 \
-v /local/path/to/protos:/protos \
-e MOCKSERVER_GRPC_PROTO_DIRECTORY=/protos \
mockserver/mockserver:latest
Replace latest with a specific version tag (e.g. mockserver/mockserver:7.0.0) to pin a known working version.
Creating gRPC Expectations
Given a proto file such as:
syntax = "proto3";
package com.example.grpc;
service GreetingService {
rpc Greeting (HelloRequest) returns (HelloResponse);
rpc ListGreetings (HelloRequest) returns (stream HelloResponse);
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string greeting = 1;
}
You can create expectations that match on the JSON-converted request body and the gRPC metadata headers:
Unary RPC
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
"httpRequest": {
"method": "POST",
"path": "/com.example.grpc.GreetingService/Greeting",
"headers": {
"x-grpc-service": ["com.example.grpc.GreetingService"],
"x-grpc-method": ["Greeting"]
},
"body": {
"type": "JSON",
"json": "{\"name\": \"World\"}"
}
},
"httpResponse": {
"statusCode": 200,
"headers": {
"grpc-status": ["0"]
},
"body": "{\"greeting\": \"Hello World\"}"
}
}'
new MockServerClient("localhost", 1080)
.when(
request()
.withMethod("POST")
.withPath("/com.example.grpc.GreetingService/Greeting")
.withHeader("x-grpc-service", "com.example.grpc.GreetingService")
.withHeader("x-grpc-method", "Greeting")
.withBody(json("{\"name\": \"World\"}"))
)
.respond(
response()
.withStatusCode(200)
.withHeader("grpc-status", "0")
.withBody("{\"greeting\": \"Hello World\"}")
);
from mockserver import MockServerClient, HttpRequest, HttpResponse, KeyToMultiValue
client = MockServerClient("localhost", 1080)
client.when(
HttpRequest(
method="POST",
path="/com.example.grpc.GreetingService/Greeting",
headers=[
KeyToMultiValue(name="x-grpc-service", values=["com.example.grpc.GreetingService"]),
KeyToMultiValue(name="x-grpc-method", values=["Greeting"])
],
body={"type": "JSON", "json": '{"name": "World"}'}
)
).respond(
HttpResponse(
status_code=200,
headers=[KeyToMultiValue(name="grpc-status", values=["0"])],
body='{"greeting": "Hello World"}'
)
)
require 'mockserver-client'
include MockServer
client = MockServer::Client.new('localhost', 1080)
client.when(
HttpRequest.new(
method: 'POST',
path: '/com.example.grpc.GreetingService/Greeting',
headers: [
{ name: 'x-grpc-service', values: ['com.example.grpc.GreetingService'] },
{ name: 'x-grpc-method', values: ['Greeting'] }
],
body: { type: 'JSON', json: '{"name": "World"}' }
)
).respond(
HttpResponse.new(
status_code: 200,
headers: [{ name: 'grpc-status', values: ['0'] }],
body: '{"greeting": "Hello World"}'
)
)
Server Streaming RPC
For server streaming RPCs, use a gRPC stream response to return multiple messages. Each message can have an optional delay:
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
"httpRequest": {
"method": "POST",
"path": "/com.example.grpc.GreetingService/ListGreetings",
"headers": {
"x-grpc-service": ["com.example.grpc.GreetingService"],
"x-grpc-method": ["ListGreetings"]
}
},
"grpcStreamResponse": {
"statusName": "OK",
"messages": [
{"json": "{\"greeting\": \"Hello Alice\"}"},
{"json": "{\"greeting\": \"Hello Bob\"}", "delay": {"timeUnit": "MILLISECONDS", "value": 100}},
{"json": "{\"greeting\": \"Hello Charlie\"}", "delay": {"timeUnit": "MILLISECONDS", "value": 200}}
]
}
}'
new MockServerClient("localhost", 1080)
.when(
request()
.withMethod("POST")
.withPath("/com.example.grpc.GreetingService/ListGreetings")
.withHeader("x-grpc-service", "com.example.grpc.GreetingService")
.withHeader("x-grpc-method", "ListGreetings")
)
.respondWithGrpcStream(
grpcStreamResponse()
.withStatusName("OK")
.withMessage("{\"greeting\": \"Hello Alice\"}")
.withMessage("{\"greeting\": \"Hello Bob\"}", delay(MILLISECONDS, 100))
.withMessage("{\"greeting\": \"Hello Charlie\"}", delay(MILLISECONDS, 200))
);
from mockserver import MockServerClient, HttpRequest, KeyToMultiValue, GrpcStreamResponse, GrpcStreamMessage, Delay
client = MockServerClient("localhost", 1080)
client.when(
HttpRequest(
method="POST",
path="/com.example.grpc.GreetingService/ListGreetings",
headers=[
KeyToMultiValue(name="x-grpc-service", values=["com.example.grpc.GreetingService"]),
KeyToMultiValue(name="x-grpc-method", values=["ListGreetings"])
]
)
).respond_with_grpc_stream(
GrpcStreamResponse(
status_name="OK",
messages=[
GrpcStreamMessage(json='{"greeting": "Hello Alice"}'),
GrpcStreamMessage(json='{"greeting": "Hello Bob"}', delay=Delay(time_unit="MILLISECONDS", value=100)),
GrpcStreamMessage(json='{"greeting": "Hello Charlie"}', delay=Delay(time_unit="MILLISECONDS", value=200))
]
)
)
require 'mockserver-client'
include MockServer
client = MockServer::Client.new('localhost', 1080)
client.when(
HttpRequest.new(
method: 'POST',
path: '/com.example.grpc.GreetingService/ListGreetings',
headers: [
KeyToMultiValue.new(name: 'x-grpc-service', values: ['com.example.grpc.GreetingService']),
KeyToMultiValue.new(name: 'x-grpc-method', values: ['ListGreetings'])
]
)
).respond_with_grpc_stream(
GrpcStreamResponse.new(
status_name: 'OK',
messages: [
GrpcStreamMessage.new(json: '{"greeting": "Hello Alice"}'),
GrpcStreamMessage.new(json: '{"greeting": "Hello Bob"}', delay: Delay.new(time_unit: 'MILLISECONDS', value: 100)),
GrpcStreamMessage.new(json: '{"greeting": "Hello Charlie"}', delay: Delay.new(time_unit: 'MILLISECONDS', value: 200))
]
)
)
Client Streaming RPC
Client streaming requests are converted with the combined stream messages in the request body. MockServer adds an x-grpc-client-streaming header to indicate this is a client streaming request. Client streaming support is limited — see Limitations below.
Bidirectional Streaming RPC (Experimental)
MockServer supports true bidirectional (bidi) gRPC streaming, where both the client and server can send messages independently and interleaved on a single stream. This requires the grpcBidiStreamingEnabled configuration flag to be set to true (default is false). See the gRPC Configuration section for details on enabling it.
A grpcBidiResponse action supports two response mechanisms:
- Eager messages — messages sent immediately when the stream is established (before any client message is received)
- Reactive rules — each inbound client message is matched against rules in order; the first rule whose matchJson matches emits its responses. The matchJson field supports exact string match and regex (via String.matches() semantics)
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
"httpRequest": {
"method": "POST",
"path": "/com.example.grpc.ChatService/BidiChat",
"headers": {
"x-grpc-service": ["com.example.grpc.ChatService"],
"x-grpc-method": ["BidiChat"]
}
},
"grpcBidiResponse": {
"statusName": "OK",
"messages": [
{"json": "{\"message\": \"Welcome to the chat!\"}"}
],
"rules": [
{
"matchJson": "{\"message\": \"hello\"}",
"responses": [
{"json": "{\"message\": \"Hello! How can I help?\"}"}
]
},
{
"matchJson": ".*goodbye.*",
"responses": [
{"json": "{\"message\": \"Goodbye! Have a nice day.\"}"}
]
}
]
}
}'
new MockServerClient("localhost", 1080)
.when(
request()
.withMethod("POST")
.withPath("/com.example.grpc.ChatService/BidiChat")
.withHeader("x-grpc-service", "com.example.grpc.ChatService")
.withHeader("x-grpc-method", "BidiChat")
)
.respondWithGrpcBidi(
grpcBidiResponse()
.withStatusName("OK")
.withMessage("{\"message\": \"Welcome to the chat!\"}")
.withRule(grpcBidiRule("{\"message\": \"hello\"}")
.withResponse("{\"message\": \"Hello! How can I help?\"}"))
.withRule(grpcBidiRule(".*goodbye.*")
.withResponse("{\"message\": \"Goodbye! Have a nice day.\"}"))
);
from mockserver import (
MockServerClient, HttpRequest, KeyToMultiValue,
GrpcBidiResponse, GrpcBidiRule, GrpcStreamMessage,
)
client = MockServerClient("localhost", 1080)
client.when(
HttpRequest(
method="POST",
path="/com.example.grpc.ChatService/BidiChat",
headers=[
KeyToMultiValue(name="x-grpc-service", values=["com.example.grpc.ChatService"]),
KeyToMultiValue(name="x-grpc-method", values=["BidiChat"])
]
)
).respond_with_grpc_bidi(
GrpcBidiResponse(
status_name="OK",
messages=[GrpcStreamMessage(json='{"message": "Welcome to the chat!"}')],
rules=[
GrpcBidiRule(
match_json='{"message": "hello"}',
responses=[GrpcStreamMessage(json='{"message": "Hello! How can I help?"}')]
),
GrpcBidiRule(
match_json='.*goodbye.*',
responses=[GrpcStreamMessage(json='{"message": "Goodbye! Have a nice day."}')]
),
]
)
)
require 'mockserver-client'
include MockServer
client = MockServer::Client.new('localhost', 1080)
client.when(
HttpRequest.new(
method: 'POST',
path: '/com.example.grpc.ChatService/BidiChat',
headers: [
KeyToMultiValue.new(name: 'x-grpc-service', values: ['com.example.grpc.ChatService']),
KeyToMultiValue.new(name: 'x-grpc-method', values: ['BidiChat'])
]
)
).respond_with_grpc_bidi(
GrpcBidiResponse.new(
status_name: 'OK',
messages: [GrpcStreamMessage.new(json: '{"message": "Welcome to the chat!"}')],
rules: [
GrpcBidiRule.new(
match_json: '{"message": "hello"}',
responses: [GrpcStreamMessage.new(json: '{"message": "Hello! How can I help?"}')]
),
GrpcBidiRule.new(
match_json: '.*goodbye.*',
responses: [GrpcStreamMessage.new(json: '{"message": "Goodbye! Have a nice day."}')]
)
]
)
)
Supported Features (Bidi Streaming)
- Times-limiting — grpcBidiResponse expectations support times(n); a times(1) bidi expectation will match once and then be exhausted, just like other expectation types.
- Request logging and verification — bidi requests are recorded in the request log and are verifiable via the verification API.
- Per-message delay — each eager message and rule response can have a per-message delay; messages are written after the configured delay elapses, chained sequentially so ordering is preserved. The top-level action delay is applied before the initial response HEADERS frame.
- Abandoned stream cleanup — if a client disconnects or abandons a bidi stream without sending END_STREAM, the expectation is cleaned up automatically (responseInProgress is cleared so the expectation can be removed when exhausted).
Requirements (Bidi Streaming)
- Bidi streaming requires the grpcBidiStreamingEnabled=true flag. Without it, a grpcBidiResponse expectation will return HTTP 501.
- Not supported in WAR/servlet deployments (returns HTTP 501).
Matching gRPC Requests
Since gRPC requests are converted to JSON, all standard MockServer matchers work:
- Path matching — match on /package.ServiceName/MethodName
- Header matching — match on x-grpc-service and x-grpc-method headers
- JSON body matching — match on the JSON-converted protobuf body using exact match, JSON Schema, JsonPath, etc.
- Regex — use regex matchers on any field
gRPC Status Codes
For unary RPCs, set the gRPC status via the grpc-status response header (as shown in the unary example above). For server streaming RPCs, use the statusName field in the grpcStreamResponse action instead. You can also set a grpc-message header (unary) or statusMessage field (streaming) to provide error details. Standard gRPC status codes are supported:
| Code | Name |
|---|---|
| 0 | OK |
| 1 | CANCELLED |
| 2 | UNKNOWN |
| 3 | INVALID_ARGUMENT |
| 4 | DEADLINE_EXCEEDED |
| 5 | NOT_FOUND |
| 6 | ALREADY_EXISTS |
| 7 | PERMISSION_DENIED |
| 8 | RESOURCE_EXHAUSTED |
| 9 | FAILED_PRECONDITION |
| 10 | ABORTED |
| 11 | OUT_OF_RANGE |
| 12 | UNIMPLEMENTED |
| 13 | INTERNAL |
| 14 | UNAVAILABLE |
| 15 | DATA_LOSS |
| 16 | UNAUTHENTICATED |
Limitations
- Unary RPC — fully supported
- Server streaming RPC — fully supported via grpcStreamResponse
- Client streaming RPC — limited support; stream messages are aggregated into a single request body
- Bidirectional streaming RPC — experimental support via grpcBidiResponse (requires grpcBidiStreamingEnabled=true); see Bidirectional Streaming above for details and known limitations
gRPC Fault Injection (Chaos)
MockServer can inject gRPC-level faults — UNAVAILABLE, DEADLINE_EXCEEDED, RESOURCE_EXHAUSTED, and other status codes — into matched RPC calls, with optional latency and request-quota controls. Faults are registered per gRPC service name via a dedicated control-plane endpoint and apply before normal request conversion in GrpcToHttpRequestHandler.
This is separate from the health-check serving-status feature described below. See the gRPC Fault Injection section on the Chaos Testing page for the full profile reference and REST API examples.
gRPC Health Checking Protocol
Kubernetes readiness and liveness probes commonly use the gRPC Health Checking Protocol (grpc.health.v1.Health/Check) to verify that a service is ready to receive traffic. MockServer auto-responds to this well-known method without requiring a proto descriptor — protobuf encoding and decoding is handled manually so health checks work out of the box even when no descriptors have been loaded.
Health checking is always available — it is built in and cannot be disabled via a configuration property; MockServer answers any gRPC request whose content-type is gRPC and whose path matches the well-known health-check method. The default serving status for all services is SERVING. You can override the status for individual services or for the global default via a REST endpoint.
ServingStatus values
| Value | Proto code | Meaning |
|---|---|---|
SERVING |
1 | The service is healthy and ready to accept requests (default) |
NOT_SERVING |
2 | The service is temporarily unavailable (probe will fail) |
UNKNOWN |
0 | Status is unknown |
SERVICE_UNKNOWN |
3 | The named service is not known to this server |
REST API
Override the status for a named service:
curl -v -X PUT "http://localhost:1080/mockserver/grpc/health" \
-H "Content-Type: application/json" \
-d '{"service": "my.payments.PaymentService", "status": "NOT_SERVING"}'
Set service to the fully-qualified gRPC service name. Use an empty string ("") to override the default status that applies to all services without an explicit override. The response confirms the registration:
{ "status": "registered", "service": "my.payments.PaymentService", "servingStatus": "NOT_SERVING" }
Read all current status overrides:
curl -v "http://localhost:1080/mockserver/grpc/health"
Returns a JSON object mapping service names to their current status. The empty string key ("_default") shows the global default:
{
"_default": "SERVING",
"my.payments.PaymentService": "NOT_SERVING"
}
All status overrides are cleared on server reset. The GET endpoint returns only services that have had their status explicitly set, plus the global default.
How it works
When MockServer receives a request whose path is exactly /grpc.health.v1.Health/Check, the request is intercepted in GrpcToHttpRequestHandler before descriptor lookup — no proto descriptor for the health service is needed. GrpcHealthCheckHandler decodes the 5-byte gRPC frame header and then manually parses the protobuf HealthCheckRequest (field 1 = service name string). It looks up the serving status in GrpcHealthRegistry, which falls back to the default status when no per-service override is registered. The response is a manually-encoded gRPC-framed HealthCheckResponse (field 1 = status enum varint). The whole path bypasses the expectation matching engine so health checks always respond, even with no expectations registered.
Server Reflection
MockServer supports the gRPC Server Reflection Protocol out of the box. Tools such as grpcurl and grpcui can use reflection to discover services and describe message types without needing a local .proto file.
Both the v1 and v1alpha reflection service paths are supported:
- /grpc.reflection.v1.ServerReflection/ServerReflectionInfo
- /grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo
The reflection service answers from the proto descriptors already loaded into MockServer (via grpcDescriptorDirectory, grpcProtoDirectory, or the REST API upload). No additional configuration is required.
Example: listing services with grpcurl
# List all services known to MockServer
grpcurl -plaintext localhost:1080 list
# Describe a specific service
grpcurl -plaintext localhost:1080 describe com.example.grpc.GreetingService
# Describe a message type
grpcurl -plaintext localhost:1080 describe com.example.grpc.HelloRequest
Limitation
MockServer's gRPC path is buffered-unary: each HTTP/2 request carries exactly one gRPC message. The reflection handler therefore processes a single ServerReflectionRequest per call. This is sufficient for grpcurl list, single symbol lookups, and single file lookups. Fully interactive bidirectional-streaming reflection (a long-lived stream with multiple back-and-forth messages) is not supported by the current pipeline.
gRPC-Web Support
MockServer supports gRPC-Web, the variant of gRPC designed for browser clients and environments that cannot use HTTP/2 trailers. gRPC-Web requests are automatically detected and translated to standard gRPC for matching against existing expectations, so no additional configuration or separate expectations are needed.
Supported Content Types
- application/grpc-web and application/grpc-web+proto — binary gRPC-Web (same length-prefixed framing as standard gRPC)
- application/grpc-web-text and application/grpc-web-text+proto — base64-encoded gRPC-Web (the entire request/response body is base64-encoded)
How It Works
When MockServer receives a request with a gRPC-Web content type:
- The request body is decoded (base64-decoded for the -text variant) and the content type is translated to application/grpc
- The request is processed through the normal gRPC pipeline — descriptor lookup, protobuf-to-JSON conversion, and expectation matching all work unchanged
- The response is re-framed as gRPC-Web: the message frame(s) are followed by a trailer frame (flag byte 0x80) containing grpc-status and grpc-message as ASCII lines in the body, instead of HTTP/2 trailers
- For the -text variant, the entire response body is base64-encoded
gRPC-Web works over both HTTP/1.1 and HTTP/2, making it suitable for browser-based gRPC clients such as grpc-web and Improbable grpc-web.
Built-in Services via gRPC-Web
The built-in gRPC health check (/grpc.health.v1.Health/Check), server reflection, and chaos fault injection all work transparently via gRPC-Web. No special configuration is needed.
Connect Protocol (connectrpc)
The Connect protocol (used by connectrpc) is not currently supported. Connect uses a different framing format (JSON or proto over standard HTTP POST with application/connect+proto content type and trailers in a JSON envelope) that is distinct from gRPC-Web. If you need Connect support, please open a feature request.
gRPC over HTTP/3
gRPC works over HTTP/3 (QUIC) as well as HTTP/2 — set the http3Port configuration property to start the HTTP/3 listener and clients can make gRPC calls over QUIC. Unary, server-streaming, and bidirectional-streaming gRPC all work over HTTP/3 with the correct trailing-HEADERS grpc-status framing. As on the TCP path, bidirectional streaming requires the grpcBidiStreamingEnabled=true flag. Expectations are matched identically regardless of whether the call arrives over HTTP/2 or HTTP/3 — no separate or HTTP/3-specific expectations are needed.
See the HTTP/3 (QUIC) Support page for details on enabling and tuning the HTTP/3 listener.
Configuration
For full details on gRPC configuration properties, see the gRPC Configuration section on the Configuration page.