Interactive Breakpoints
Interactive breakpoints let you pause HTTP exchanges — forwarded/proxied, matched mock responses, and unmatched-404 responses — inspect and optionally modify the request, response, or individual stream frames, and then continue or abort. This is useful for debugging integrations, testing error handling, and inspecting streaming traffic (SSE, gRPC, WebSocket, GraphQL subscription) frame by frame.
Breakpoints are off by default — there is zero overhead when no matchers are registered. REQUEST and RESPONSE phases cover forwarded/proxied exchanges, matched mock responses (RESPONSE, RESPONSE_TEMPLATE, RESPONSE_CLASS_CALLBACK), and unmatched-404 responses. Stream-frame phases (RESPONSE_STREAM, INBOUND_STREAM) remain scoped to forwarded and mock-generated streaming traffic.
- How it works
- Breakpoint phases
- Registering a matcher
- Resolving via the callback WebSocket
- Dashboard UI
- Client examples
- REST API reference
- Safety rails
How It Works
A breakpoint is a request matcher + set of phases. When MockServer forwards a request that matches the matcher, the exchange is paused at each requested phase. Resolution is interactive: a connected client (or the dashboard) receives the paused item over a callback WebSocket, inspects it, and sends a decision. The server applies the decision and resumes the exchange.
1. Register a matcher PUT /mockserver/breakpoint/matcher
{ "httpRequest": {...}, "phases": ["REQUEST","RESPONSE"], "clientId": "..." }
2. A proxied request GET /api/users → matched → paused at REQUEST phase
matches the matcher
3. Server pushes /_mockserver_callback_websocket → client or dashboard receives paused request
4. Client decides → Continue (forward original)
→ Modify (forward a changed request)
→ Abort (return error without forwarding)
5. Server applies the decision and resumes the exchange
The callback WebSocket (/_mockserver_callback_websocket) is the same endpoint that object-callback expectations use. All supported language clients and the dashboard speak the same protocol, so a breakpoint registered by the Java client can be inspected with identical server-side behaviour as one registered via the dashboard.
Breakpoint Phases
You can pause an exchange at four points in its lifecycle:
| Phase constant | What is paused | Available actions |
|---|---|---|
| REQUEST | The request, before it is forwarded upstream (for proxied/forwarded exchanges), fed into response generation (for matched mock responses), or answered with a 404 (for unmatched requests). Covers forwarded exchanges, matched mock responses (RESPONSE, RESPONSE_TEMPLATE, RESPONSE_CLASS_CALLBACK), and unmatched-404 responses. | Continue (proceed unchanged), Modify (proceed with a changed request), Abort (return error to client) |
| RESPONSE | The response, after it is generated or received but before it is written to the client. Covers forwarded upstream responses, matched mock responses, and unmatched-404 responses. Non-streaming (buffered) responses only — streaming responses use RESPONSE_STREAM. | Continue (write unchanged), Modify (write a changed response), Abort (return error to client) |
| RESPONSE_STREAM | Each individual frame of a streaming response (SSE, HTTP/1.1 chunked, gRPC server-streaming, WebSocket, GraphQL subscription) | Continue, Modify (replace body), Drop (discard frame), Inject (write original + extra frame), Close (end stream) |
| INBOUND_STREAM | Each client-to-server frame on a bidirectional connection (WebSocket, GraphQL subscription, and gRPC bidirectional over both HTTP/2 and HTTP/3) | Continue, Modify (replace body), Drop (discard frame), Inject (process original + extra frame), Close (close connection) |
Registering a Matcher
Breakpoints are activated by registering a request matcher. There are no global on/off flags — a breakpoint fires only for requests that match a registered matcher at the specified phases.
Register with PUT /mockserver/breakpoint/matcher:
# Pause all forwarded GET /api/users requests — hold at both request and response phases
# clientId must be the id of a connected callback-WebSocket client
curl -X PUT http://localhost:1080/mockserver/breakpoint/matcher \
-H "Content-Type: application/json" \
-d '{
"httpRequest": {
"method": "GET",
"path": "/api/users"
},
"phases": ["REQUEST", "RESPONSE"],
"clientId": "my-ws-client-id"
}'
# Response: { "id": "a1b2c3d4-...", "phases": ["REQUEST", "RESPONSE"], "clientId": "my-ws-client-id" }
The httpRequest body uses the same matcher fields as an expectation request matcher: method, path (supports regex), headers, queryStringParameters, body, etc. See Creating Expectations for the full matcher reference.
The id returned is a UUID you can use to remove the matcher later. Registrations persist until explicitly removed or until PUT /mockserver/reset is called.
The required clientId field
Every breakpoint matcher registration requires a clientId field that identifies the callback-WebSocket client (a language client or the dashboard) to dispatch paused items to for interactive resolution:
curl -X PUT http://localhost:1080/mockserver/breakpoint/matcher \
-H "Content-Type: application/json" \
-d '{
"httpRequest": { "path": "/api/.*" },
"phases": ["REQUEST", "RESPONSE"],
"clientId": "my-ws-client-id"
}'
The server dispatches the paused item over the callback WebSocket to that client for interactive resolution. Omitting clientId returns a 400 error.
Matcher management endpoints
| Method | Path | Description |
|---|---|---|
| PUT | /mockserver/breakpoint/matcher | Register a matcher. Body: {"httpRequest": {...}, "phases": [...], "clientId": "..."} (clientId is required). Returns {"id": "...", "phases": [...]}. |
| GET / PUT | /mockserver/breakpoint/matchers | List all registered matchers. Returns {"matchers": [{id, httpRequest, phases, clientId}]}. |
| PUT | /mockserver/breakpoint/matcher/remove | Remove a matcher by id. Body: {"id": "..."}. Returns {"status": "removed", "id": "..."} or 404. |
| PUT | /mockserver/breakpoint/matcher/clear | Remove all matchers. Returns {"status": "cleared", "count": N}. |
Resolving via the Callback WebSocket (Recommended)
The callback WebSocket (/_mockserver_callback_websocket) is the primary resolution mechanism when using a language client or the dashboard. Each client connects once, receives a server-assigned clientId, and passes it when registering matchers. The server then dispatches paused items only to that client.
Multiple clients can coexist — each breakpoint is owned by one client and dispatched only to it. When a client disconnects, the server automatically removes its matchers and auto-continues any in-flight dispatches.
REQUEST and RESPONSE phases
When a request/response exchange is paused, the server pushes it to the owning client over the callback WS. The client replies with:
- REQUEST phase — an HttpRequest to continue/modify the outbound request, or an HttpResponse to abort (write that response to the downstream client without forwarding).
- RESPONSE phase — an HttpResponse to write (continue with the original, or a modified replacement).
The matched breakpoint id is conveyed via the X-MockServer-BreakpointId header on the dispatched HttpRequest. This lets clients with multiple registered matchers route each paused item to the correct handler.
RESPONSE_STREAM and INBOUND_STREAM phases (per-frame protocol)
For streaming phases, each held frame is dispatched as a PausedStreamFrameDTO message over the callback WS. The client sends back a StreamFrameDecisionDTO.
Server to client: PausedStreamFrameDTO
| Field | Type | Description |
|---|---|---|
| correlationId | String | Unique per paused frame; the client MUST echo this in the reply |
| streamId | String | Groups frames from the same forwarded response or connection |
| sequenceNumber | int | 0-based, monotonically increasing within the stream |
| direction | String | "OUTBOUND" (server-to-client frames) or "INBOUND" (client-to-server frames) |
| phase | String | "RESPONSE_STREAM" or "INBOUND_STREAM" |
| body | String | Frame payload, Base64-encoded (RFC 4648, no line breaks). Frame bytes are protocol-specific — gRPC is length-prefixed, WebSocket may be text or binary, SSE is UTF-8 text. |
| requestMethod | String (nullable) | HTTP method of the original request |
| requestPath | String (nullable) | Path of the original request |
| breakpointId | String (nullable) | Id of the matched breakpoint matcher, for per-breakpoint handler routing |
Client to server: StreamFrameDecisionDTO
| Field | Type | Description |
|---|---|---|
| correlationId | String | MUST match PausedStreamFrameDTO.correlationId |
| action | String | One of: CONTINUE, MODIFY, DROP, INJECT, CLOSE |
| body | String (nullable) | Base64-encoded replacement or injected bytes; required for MODIFY and INJECT |
| Action | Effect |
|---|---|
| CONTINUE | Write the original frame unchanged |
| MODIFY | Write the body bytes instead of the original |
| DROP | Discard the frame (the downstream client never sees it) |
| INJECT | Write the original frame AND an additional frame with the body bytes |
| CLOSE | End the stream at this frame (sends a final close signal and evicts all remaining held frames) |
gRPC framing note: for gRPC streams, replacement bytes for MODIFY and INJECT must be a valid gRPC length-prefixed frame (1-byte compressed flag + 4-byte big-endian message length + message bytes). The breakpoint engine passes bytes through opaquely and does not re-frame the content.
Dashboard UI
The MockServer dashboard includes a Breakpoints panel — the no-code way to use breakpoints. The dashboard is a real callback-WebSocket client: it connects to /_mockserver_callback_websocket exactly as a language client does, receives a server-assigned clientId, and uses it when registering matchers. The Breakpoints panel has three tabs:
Matchers tab
A form to register a breakpoint matcher — enter a method (optional), a path pattern (supports regex), and check which phases to intercept. On submit, the dashboard calls PUT /mockserver/breakpoint/matcher with its own clientId. The tab lists all registered matchers with remove and clear-all actions.
Live Exchanges tab
When a REQUEST or RESPONSE phase breakpoint fires on a forwarded exchange, the server pushes it to the dashboard over the callback WS. The tab shows it in real time. Buttons:
- Continue — send the original request/response unchanged
- Modify — open a JSON editor, change the request or response, send the modified version
- Abort — REQUEST phase only; returns a 503 to the downstream client without forwarding
Live Streams tab
Paused stream frames (RESPONSE_STREAM and INBOUND_STREAM phases) are shown in real time with direction, stream ID, sequence number, and a decoded body preview. Buttons: Continue, Modify (edit body and re-encode), Drop, Inject (extra frame after the held one), Close (end the stream).
The Breakpoints panel shows a connection-state indicator (green = connected, yellow = reconnecting, red = disconnected). It reconnects automatically with exponential backoff on disconnect.
Client Examples
All supported language clients expose a breakpoint API that opens a callback WebSocket connection, registers matchers with the assigned clientId, and invokes handler callbacks when the server dispatches paused items. The WebSocket connection is opened lazily on the first addBreakpoint call and reused for all subsequent registrations from the same client instance.
PHP is not supported for breakpoints — breakpoints require a persistent callback WebSocket connection for resolution, and PHP runtimes lack WebSocket client support. There is no alternative resolution path; PHP cannot use interactive breakpoints.
import org.mockserver.client.MockServerClient;
import org.mockserver.mock.breakpoint.BreakpointPhase;
MockServerClient client = new MockServerClient("localhost", 1080);
// REQUEST phase only — return a modified request or an HttpResponse to abort
String id = client.addBreakpoint(
request().withPath("/api/.*"),
request -> request.withHeader("Authorization", "Bearer test-token") // BreakpointRequestHandler
);
// REQUEST + RESPONSE
String id2 = client.addBreakpoint(
request().withPath("/api/.*"),
request -> request, // BreakpointRequestHandler
(request, response) -> response.withBody("{}") // BreakpointResponseHandler
);
// Streaming phases (RESPONSE_STREAM / INBOUND_STREAM)
String id3 = client.addBreakpoint(
request().withPath("/stream/.*"),
EnumSet.of(BreakpointPhase.RESPONSE_STREAM),
null, null,
frame -> new StreamFrameDecisionDTO() // BreakpointStreamFrameHandler
.setCorrelationId(frame.getCorrelationId())
.setAction("CONTINUE")
);
// Manage matchers
client.listBreakpointMatchers();
client.removeBreakpointMatcher(id);
client.clearBreakpointMatchers();
const mockserver = require('mockserver-client').mockServerClient;
const client = mockserver('localhost', 1080);
// REQUEST phase only
client.addRequestBreakpoint(
{ path: '/api/.*' },
function (request) {
request.headers = request.headers || [];
request.headers.push({ name: 'Authorization', values: ['Bearer test-token'] });
return request; // return an HttpResponse object to abort instead
}
).then(function (id) { console.log('registered:', id); });
// REQUEST + RESPONSE
client.addRequestAndResponseBreakpoint(
{ path: '/api/.*' },
function (request) { return request; },
function (request, response) { return response; }
);
// Streaming phases
client.addBreakpoint(
{ path: '/stream/.*' },
['RESPONSE_STREAM'],
null, // no request handler
null, // no response handler
function (frame) {
// frame: { correlationId, streamId, sequenceNumber, direction, phase, body (base64) }
return { action: 'CONTINUE' };
// Other actions: MODIFY (with body), DROP, INJECT (with body), CLOSE
}
);
// Manage matchers
client.listBreakpointMatchers().then(result => console.log(result.matchers));
client.removeBreakpointMatcher(id);
client.clearBreakpointMatchers();
from mockserver import MockServerClient, HttpRequest, HttpResponse
client = MockServerClient("localhost", 1080)
# REQUEST phase only
bp_id = client.add_request_breakpoint(
HttpRequest(path="/api/.*"),
lambda request: request, # continue unchanged; return HttpResponse to abort
)
# REQUEST + RESPONSE
bp_id2 = client.add_request_and_response_breakpoint(
HttpRequest(path="/api/.*"),
lambda request: request,
lambda request, response: response,
)
# Streaming phases
bp_id3 = client.add_breakpoint(
HttpRequest(path="/stream/.*"),
["RESPONSE_STREAM"],
stream_frame_handler=lambda frame: {"action": "CONTINUE"},
# Other actions: MODIFY (with body), DROP, INJECT (with body), CLOSE
)
# Manage matchers
matchers = client.list_breakpoint_matchers() # {"matchers": [...]}
client.remove_breakpoint_matcher(bp_id)
client.clear_breakpoint_matchers()
# Async client: AsyncMockServerClient exposes the same methods as coroutines
require 'mockserver'
client = MockServer::Client.new('localhost', 1080)
# REQUEST phase only
bp_id = client.add_request_breakpoint(
MockServer::HttpRequest.new(path: '/api/.*'),
->(request) { request } # continue unchanged; return HttpResponse to abort
)
# REQUEST + RESPONSE
bp_id2 = client.add_request_and_response_breakpoint(
MockServer::HttpRequest.new(path: '/api/.*'),
->(request) { request },
->(request, response) { response }
)
# Streaming phases
bp_id3 = client.add_breakpoint(
MockServer::HttpRequest.new(path: '/stream/.*'),
%w[RESPONSE_STREAM],
stream_frame_handler: ->(frame) { { 'action' => 'CONTINUE' } }
# Other actions: MODIFY (with body), DROP, INJECT (with body), CLOSE
)
# Manage matchers
matchers = client.list_breakpoint_matchers # {"matchers" => [...]}
client.remove_breakpoint_matcher(bp_id)
client.clear_breakpoint_matchers
import (
mockserver "github.com/mock-server/mockserver-monorepo/mockserver-client-go"
)
client := mockserver.NewClient("localhost", 1080)
// REQUEST-only breakpoint
id, _ := client.AddRequestBreakpoint(
mockserver.Request().Path("/api/.*"),
func(req map[string]interface{}) interface{} {
return req // continue with original; return a response map to abort
},
)
// REQUEST + RESPONSE breakpoint
id2, _ := client.AddRequestResponseBreakpoint(
mockserver.Request().Path("/api/.*"),
func(req map[string]interface{}) interface{} { return req },
func(req, resp map[string]interface{}) map[string]interface{} { return resp },
)
// Streaming breakpoint
id3, _ := client.AddStreamBreakpoint(
mockserver.Request().Path("/stream/.*"),
[]mockserver.BreakpointPhase{mockserver.PhaseResponseStream},
func(frame *mockserver.PausedStreamFrame) *mockserver.StreamFrameDecision {
d := mockserver.ContinueFrame(frame.CorrelationID)
return &d
// Other helpers: ModifyFrame, DropFrame, InjectFrame, CloseFrame
},
)
// Manage matchers
list, _ := client.ListBreakpointMatchers()
client.RemoveBreakpointMatcher(id)
client.ClearBreakpointMatchers()
client.CloseBreakpointWebSocket()
using MockServer.Client;
using MockServer.Client.Models;
using System.Text.Json.Nodes;
var client = new MockServerClient("localhost", 1080);
// REQUEST-only breakpoint
var id = client.AddRequestBreakpoint(
HttpRequest.Request().WithPath("/api/.*").Build(),
request => request // continue with original; return an HttpResponse to abort
);
// REQUEST + RESPONSE breakpoint
var id2 = client.AddRequestResponseBreakpoint(
HttpRequest.Request().WithPath("/api/.*").Build(),
request => request,
(request, response) => response
);
// Streaming breakpoint
var id3 = client.AddStreamBreakpoint(
HttpRequest.Request().WithPath("/stream/.*").Build(),
new[] { BreakpointPhase.ResponseStream },
frame => StreamFrameDecision.Continue(frame.CorrelationId!)
// Other helpers: .Modify, .Drop, .Inject, .Close
);
// Manage matchers
var list = client.ListBreakpointMatchers();
client.RemoveBreakpointMatcher(id);
client.ClearBreakpointMatchers();
use mockserver_client::*;
let client = ClientBuilder::new("localhost", 1080).build()?;
// REQUEST-only breakpoint
let id = client.add_request_breakpoint(
HttpRequest::new().path("/api/.*"),
Box::new(|req| Some(req)), // continue with original; return None to use original
)?;
// REQUEST + RESPONSE breakpoint
let id2 = client.add_request_response_breakpoint(
HttpRequest::new().path("/api/.*"),
Box::new(|req| Some(req)),
Box::new(|_req, resp| Some(resp)),
)?;
// Streaming breakpoint
let id3 = client.add_stream_breakpoint(
HttpRequest::new().path("/stream/.*"),
&[phase::RESPONSE_STREAM],
Box::new(|frame| {
Some(StreamFrameDecision::continue_frame(&frame.correlation_id))
// Other helpers: ::modify, ::drop_frame, ::inject, ::close
}),
)?;
// Manage matchers
let list = client.list_breakpoint_matchers()?;
client.remove_breakpoint_matcher(&id)?;
client.clear_breakpoint_matchers()?;
client.close_breakpoint_websocket();
REST API Reference
Matcher registration
| Method | Path | Description |
|---|---|---|
| PUT | /mockserver/breakpoint/matcher | Register a matcher. Body: {"httpRequest":{...}, "phases":[...], "clientId":"..."}. Returns {"id":"...", "phases":[...]}. |
| GET/PUT | /mockserver/breakpoint/matchers | List all matchers: {"matchers":[{id, httpRequest, phases, clientId?}]} |
| PUT | /mockserver/breakpoint/matcher/remove | Remove by id: {"id":"..."} |
| PUT | /mockserver/breakpoint/matcher/clear | Remove all matchers |
Safety Rails
| Property | Env var | Default | Description |
|---|---|---|---|
| mockserver.breakpointTimeoutMillis | MOCKSERVER_BREAKPOINT_TIMEOUT_MILLIS | 30000 | Auto-continue timeout in milliseconds. A paused exchange or frame that is not resolved within this period is automatically continued with the original request/response/frame. Shared across all phases. |
| mockserver.breakpointMaxHeld | MOCKSERVER_BREAKPOINT_MAX_HELD | 50 | Maximum number of concurrently paused exchanges and frames (across all phases and registries). When the cap is reached, new intercepts are skipped — the exchange proceeds normally. |
Additional safety behaviours:
- Non-blocking: the breakpoint mechanism is fully asynchronous. No Netty event-loop or scheduler thread is blocked while waiting for a decision.
- Stream-close eviction: when a stream completes or errors, all held frames for that stream are automatically continued, preventing resource leaks and hanging futures.
- Disconnect cleanup: when a callback-WebSocket client disconnects, the server removes all its breakpoint matchers and auto-continues any in-flight dispatches.
- Frame ordering: frames within a stream must be resolved in sequence — attempting to resolve frame N before frame N-1 is rejected.
Related Pages
- Configuration Properties — full reference for breakpointTimeoutMillis and breakpointMaxHeld
- MockServer Dashboard — includes the Breakpoints panel
- Creating Expectations — request matcher field reference
- MockServer Clients — client library overview