--- title: Mocking gRPC Services description: Mock gRPC unary, server-streaming, client-streaming, and bidirectional calls in MockServer using proto descriptors or JSON expectations. shortTitle: gRPC Mocking layout: page pageOrder: 1 section: 'Protocols & Advanced' subsection: true sitemap: priority: 0.8 changefreq: 'monthly' lastmod: 2026-05-12T00:00:00+00:00 ---  

See it in action

MockServer converts gRPC (protobuf over HTTP/2) to JSON internally so you can mock gRPC services with the same expectation format used for REST. It also handles the rest of a real gRPC stack out of the box: server reflection (so grpcurl / grpcui can discover your services), gRPC-Web for browser clients, and the built-in gRPC Health Checking Protocol (which works even before any descriptor is loaded). Here is the minimal end-to-end flow:

  1. Compile your .proto to a descriptor set and mount it in Docker (or point at the directory with MOCKSERVER_GRPC_DESCRIPTOR_DIRECTORY):
mkdir -p descriptors
protoc --descriptor_set_out=descriptors/service.dsc --include_imports service.proto

# mount the whole descriptors directory and point MockServer at it
docker run -d --rm \
  -p 1080:1080 \
  -v $(pwd)/descriptors:/descriptors \
  -e MOCKSERVER_GRPC_DESCRIPTOR_DIRECTORY=/descriptors \
  mockserver/mockserver

MOCKSERVER_GRPC_DESCRIPTOR_DIRECTORY loads compiled .dsc descriptor sets (as above). To let MockServer compile raw .proto files at startup instead, mount the proto directory and use MOCKSERVER_GRPC_PROTO_DIRECTORY. See Loading proto descriptors below for all three loading options.

  1. Create a gRPC expectation (MockServer adds x-grpc-service and x-grpc-method headers automatically; match on them to target a specific RPC):
curl -s -X PUT http://localhost:1080/mockserver/expectation \
  -H "Content-Type: application/json" \
  -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\"}"
    }
  }'
  1. Call the gRPC endpoint from your application or a gRPC client — MockServer encodes the JSON response back to protobuf binary and returns it as a valid gRPC response.

You only need the gRPC headers on the request matcher. MockServer remembers which service and method the incoming call was for, so the response above needs no x-grpc-service or x-grpc-method — just the JSON body. Setting them explicitly on the response still works and takes precedence, which is useful when one expectation serves calls to more than one RPC.

Where grpc-status ends up. gRPC clients require the final status in a trailer (a terminal trailing HEADERS frame), not a normal response header. You still write it as a header on the expectation — as in the example above — and MockServer moves it to the trailer for you, alongside grpc-message when set. The only real header on the response is content-type: application/grpc. To return an error status, either give grpc-status its numeric code (e.g. "5") or use grpc-status-name with the name (e.g. "NOT_FOUND"), optionally with a grpc-message description. gRPC-Web is the exception: browsers cannot read HTTP/2 trailers, so for gRPC-Web requests MockServer embeds the status in a trailer frame at the end of the response body instead — no change to how you write the expectation.

Skip the response body and let MockServer synthesize one. If a matched gRPC expectation has a successful (grpc-status: 0) response with no response body, MockServer builds a schema-valid example message from the loaded descriptor's response type and returns that — so a client receives a well-formed, type-correct protobuf message instead of an empty frame. Every field is populated with a deterministic placeholder (strings as "string", numbers as 0, booleans as true, enums as their first declared value, repeated fields as a single element, nested messages recursively, and well-known types such as google.protobuf.Timestamp as canonical values). This is handy for quickly stubbing an endpoint when you only care that a valid response comes back. Provide an explicit response body whenever you need specific values — an explicit body is always used as-is and never overwritten.

When no expectation matches. Synthesis only ever applies to a matched expectation. A gRPC call that matches nothing comes back as grpc-status: 12 (UNIMPLEMENTED) — the same status a real gRPC server returns for a method it does not implement — with a grpc-message naming the underlying HTTP 404. MockServer never invents a successful response for a call it has no expectation for, so a typo in the service or method name, or a descriptor that does not match your expectations, shows up as a clear client-side error rather than a plausible-looking reply. This applies over HTTP/1.1, HTTP/2 and HTTP/3.

That is one case of a general rule: on a gRPC path, any response with a non-2xx HTTP status and no grpc-status of its own is returned as a gRPC error, using the mapping from the gRPC specification — 404 to UNIMPLEMENTED, 401 to UNAUTHENTICATED, 403 to PERMISSION_DENIED, 400 to INTERNAL, 429/502/503/504 to UNAVAILABLE, and anything else to UNKNOWN. The response body is dropped, since it is not a protobuf message of the method's response type. This also covers an expectation you deliberately give a non-2xx status, and a non-2xx response from the real server when you are using MockServer as a gRPC forward proxy. If you want an error status and a body, or want to choose the code yourself, set grpc-status or grpc-status-name explicitly — an explicit gRPC status always wins over the HTTP status.

Client deadlines are honoured. If your client sets a deadline (for example gRPC-Java's withDeadlineAfter, which sends a grpc-timeout header) and it passes before MockServer has written the response, the call comes back as grpc-status: 4 (DEADLINE_EXCEEDED) and the late response is discarded. This matters when you combine a deadline with a delay: an expectation that delays longer than the client's deadline now returns DEADLINE_EXCEEDED from the server, rather than the client giving up locally while MockServer carries on writing. The header is still an ordinary request header, so you can also match on it. This applies over HTTP/1.1, HTTP/2, HTTP/3 and gRPC-Web.

Deadlines apply to streaming calls too, and are enforced mid-stream: if the deadline passes part-way through a server-streaming, client-streaming or bidirectional call, MockServer ends the stream there with grpc-status: 4 and stops sending further messages — so a long stream of delayed messages will not keep being written after your client has stopped listening.

Error messages can contain anything. grpc-message is percent-encoded on the wire as the gRPC specification requires, so accented characters, other scripts, emoji, newlines and literal % signs survive the round trip — write the plain text and MockServer handles the encoding. A spec-conformant client decodes it back to exactly what you wrote.

Message size limits. A gRPC request message larger than maxGrpcMessageSize (default 4 MB, the same default as gRPC-Java and gRPC-Go) is rejected with grpc-status: 8 (RESOURCE_EXHAUSTED). Raise the setting if you deliberately mock large messages. If your client compresses with an algorithm MockServer cannot read, the call returns grpc-status: 12 (UNIMPLEMENTED) together with a grpc-accept-encoding: identity, gzip header telling it what to use instead.

See the gRPC examples in the examples/bruno/grpc and examples/curl/grpc_stream folders. The detailed reference (streaming RPCs, descriptor upload, Docker configuration) follows below.

{% include_subpage _includes/grpc_mocking.html %}