High-performance Mountebank-compatible HTTP/HTTPS mock server written in Rust
Rift is a Mountebank-compatible mock server written in Rust. Its throughput stays flat as your stub file grows — on an M4 laptop, 211,378 → 209,523 RPS between the first and the last stub of the same 310-stub imposter, where Mountebank falls 8,546 → 1,344 across that same pair — and the same engine embeds in-process in Java, Node, Go and Scala. It loads your existing imposters.json unchanged.
Documentation | Quick Start | Examples
- Same REST API - Works with existing Mountebank clients and tooling
- Same Configuration - Load your
imposters.jsonwithout changes - Same Behavior - Predicates, responses, behaviors all work identically
The slope matters more than the multiple. A mock server's job is to decide which of your stubs matches a request, and the straightforward way to do that is to try each one in turn — so its cost grows with a config file that only ever grows. Rift indexes stubs instead of scanning them. Measured against stub position alone, holding the corpus and the predicate shape fixed: moving from the first stub of a 310-stub imposter to the last costs Rift 0.9% of its throughput and Mountebank 84%. How the matching works.
Both engines, same machine, same load — measured on two very different hosts so you can see how much of the gap is the engine and how much is the hardware:
| Workload | Apple M4 laptop Mountebank → Rift | AMD EPYC 9V74, 16 vCPU Mountebank → Rift |
|---|---|---|
| Simple static stub | 8,898 → 214,818 RPS (24x) | 5,982 → 324,952 RPS (54x) |
| Deep path match (310 stubs) | 1,344 → 209,523 RPS (156x) | 542 → 322,530 RPS (595x) |
| Complex AND/OR predicates | 4,703 → 191,987 RPS (41x) | 1,814 → 259,548 RPS (143x) |
| JSON body equals | 7,611 → 199,670 RPS (26x) | 2,730 → 294,294 RPS (108x) |
| JSONPath predicate | 4,312 → 199,404 RPS (46x) | 1,921 → 304,796 RPS (159x) |
| XPath predicate | 5,542 → 187,869 RPS (34x) | 1,966 → 247,897 RPS (126x) |
| Regex path (100 patterns) | 112 → 207,024 RPS (1,857x) | 52 → 317,851 RPS (6,160x) |
Read the two columns together, not separately. Rift gets faster with more cores (215k → 325k); Mountebank gets slower (8,898 → 5,982), because it is single-threaded and the server's individual cores are slower than the laptop's. So the EPYC multipliers are inflated at both ends — the M4 column is the more conservative read, and it is still 24x–1,857x.
Measured 2026-07-20 — Rift built from master (924cf73) vs Mountebank 2.9.1, native
processes (no Docker), oha at 50 keep-alive connections, 20s/scenario after warmup, each engine
run alone on the same machine. Each figure is the median of 3 repetitions; per-scenario spread was
≤12% on the M4 (a laptop thermally throttles over a 30-minute run — both engines lost ~7% between
the first and last repetition) and ≤5% on EPYC. Throughput scales with matching complexity: Rift
stays flat while Mountebank's per-request cost grows with stub count and predicate type. Full
methodology and all 13 scenarios: tests/benchmark. Your numbers will vary
with hardware and config.
WireMock is the most widely used JVM mock server. Same suite, same host, same load — Rift is 4.0x–14.0x its throughput, and the gap widens as matching work grows:
| Workload | WireMock → Rift | p99 |
|---|---|---|
| Simple static stub | 83,048 → 334,025 RPS (4.0x) | 7.0 ms → 2.4 ms |
| Deep path match (310 stubs) | 24,264 → 326,779 RPS (13.5x) | 31.6 ms → 2.5 ms |
| Regex path (100 patterns) | 48,982 → 311,815 RPS (6.4x) | — |
| JSON body equals | 63,814 → 314,939 RPS (4.9x) | — |
| Query match (last of 100) | 20,529 → 190,867 RPS (9.3x) | — |
Rift stays roughly flat from a trivial stub to a 310-stub deep match (334k → 327k); WireMock falls from 83k to 24k. That shape matters more than the headline multiple.
This is an architecture comparison, not a quality judgement. WireMock is mature, well-engineered, and far more widely adopted than Rift, with a much larger ecosystem — extensions, a Kotlin DSL, OpenAPI-driven mocking, commercial support — and its in-process story on the JVM is genuinely good. If you are an all-JVM shop and WireMock is working for you, Rift's case is weaker. Rift vs WireMock covers both directions, including when not to switch.
Intel Xeon Platinum 8573C, 16 vCPU (GitHub ubuntu-16core), 2026-07-27. WireMock 3.9.1 on
Temurin 21 vs Rift from master. oha at 256 keep-alive connections, 20s/scenario after a 10s
warmup — identical for both engines, each run alone. Median of 3 reps; spread ≤5.6% (Rift ≤1.2%).
WireMock's Jetty pool is pinned to 256 so its 10-thread default is not the ceiling — a fairness
guarantee, not a speedup: the stock-default column lands within noise of it on this hardware. Its
request journal is off, matching how Rift and Mountebank are measured. Ratios move with offered
concurrency — the same suite at 50 connections measures 3.1x-8.1x — so always quote the
connection count. Full methodology, both connection points, all 13 scenarios and two caveats we do
not bury: docs/performance.
Microcks is the Apache-2.0, CNCF-incubating alternative, and the one many teams reach for before a commercial tool. Same suite, same load — Rift is 13.6x–54.6x its throughput on the HTTP matching path:
| Workload | Microcks → Rift | p99 |
|---|---|---|
| Simple static stub | 16,192 → 347,604 RPS (21.5x) | 78.9 ms → 2.3 ms |
| API first stub (1st of 310) | 6,457 → 338,592 RPS (52.4x) | 239.3 ms → 2.4 ms |
| Deep path match (310 stubs) | 6,420 → 338,404 RPS (52.7x) | 238.0 ms → 2.4 ms |
| No match | 6,487 → 354,170 RPS (54.6x) | 166.2 ms → 2.3 ms |
| Query match (last of 100) | 14,397 → 195,966 RPS (13.6x) | 74.8 ms → 4.0 ms |
That multiple is larger than WireMock's, and it is the least interesting thing here. Microcks is not a slower mock server so much as a different kind of product: a spec-driven mocking and contract testing platform, with a web UI, multi-tenancy and eight protocols, built on Spring Boot with a datastore behind it. Raw stub-serving throughput is not what it was built for, and for most of its users it is not the binding constraint. Quoting 54x without that context would be misleading.
The genuinely interesting result is the shape. Microcks' first, middle and last API points land within 0.6% of each other, so — like Rift, and unlike WireMock and Mountebank — stub position costs it nothing; it resolves by path and verb rather than scanning candidates. So against Microcks Rift's advantage is absolute throughput and tail latency, not scan behaviour, and the "competitors pay per candidate stub" story does not apply to it.
Where Microcks is better, plainly: it generates mocks from OpenAPI/AsyncAPI/Postman specs so they cannot drift from your contract, it does contract testing against a live implementation, it speaks seven protocols Rift does not (AsyncAPI, Kafka, MQTT, AMQP, WebSocket, gRPC, GraphQL), and it has CNCF governance behind it. Rift has none of that. If your workflow starts from a spec or leaves HTTP, Microcks is the right tool and this table is beside the point. Rift vs Microcks covers both directions.
AMD EPYC 7763, 16 vCPU (GitHub ubuntu-16core), 2026-07-30. Microcks 1.14.0 on Temurin 21 as a
native JVM (no container — a container's virtualised network is not a property of the engine) vs Rift
from source. oha at 256 keep-alive connections, 20s/scenario after a 10s warmup, each engine run
alone. Median of 3 reps; spread ≤3.3% (Rift ≤2.7%). Microcks' per-request invocation statistics and
CORS policy are off, matching how WireMock's request journal is off and how Rift is measured — that
change turned out to be worth nothing measurable (−4% to +2%), and the stock-defaults column is
published anyway. Six of the 13 scenarios are comparable; the other seven are listed with the reason
each is excluded rather than approximated. Full methodology, the stock-defaults column, and what the
data does not support: docs/comparisons/microcks.
Everything Mountebank does:
- Imposters - HTTP/HTTPS mock servers
- Predicates - equals, contains, matches, exists, jsonpath, xpath, and, or, not
- Responses - Static, proxy, injection
- Behaviors - wait, decorate, copy, lookup
- Proxy Mode - Record and replay
...and a good deal it doesn't:
| Fault Injection | Probabilistic latency, error, and TCP faults — chaos testing without a sidecar |
| Scripting | Rhai and JavaScript engines for dynamic responses, with a script check/script run CLI |
| Scenarios (FSM) | Declarative state machines instead of hand-rolled stateful injection |
| Flow State | Per-flow key/value store, in-memory or Redis-backed |
| Correlated Isolation | Per-flow stub and state partitioning, so parallel tests don't collide |
| Front Door | One listener routing to many imposters by host, path, header or method |
| Single-Port Gateway | Reach every imposter through the admin port |
| Intercept Proxy | TLS-MITM a hard-coded external HTTPS host — no mitmproxy needed |
| Stub Analysis | Overlap and conflict detection before a stub silently shadows another |
| Debug Mode | X-Rift-Debug explains why a request matched, or didn't |
| Hot Reload | Re-read config without dropping the process |
| Metrics | Prometheus endpoint |
| Linting | rift-lint validates configs in CI before they ever load |
| Terminal UI | rift-tui for interactive imposter management |
| Embedding & FFI | Run the engine in-process from Rust, or any language over the C ABI |
Four official SDKs — Java, Scala, Node/TypeScript, Go — wrap all of it behind a typed DSL, and all four replay the same conformance corpus. See Language SDKs.
# Pull and run
docker pull zainalpour/rift-proxy:latest
docker run -p 2525:2525 -p 4545:4545 zainalpour/rift-proxy:latest
# Create your first imposter
curl -X POST http://localhost:2525/imposters \
-H "Content-Type: application/json" \
-d '{ "port": 4545, "protocol": "http", "stubs": [{ "predicates": [{ "equals": { "path": "/hello" } }], "responses": [{ "is": { "statusCode": 200, "body": "Hello, World!" } }] }] }'# Test it
curl http://localhost:4545/hello# Load your existing imposters.json
docker run -p 2525:2525 -v $(pwd)/imposters.json:/imposters.json \
zainalpour/rift-proxy:latest --configfile /imposters.jsondocker pull zainalpour/rift-proxy:latestbrew tap achird-labs/rift
brew install riftcargo install rift-http-proxyDownload pre-built binaries from GitHub Releases:
# Example for Linux x86_64 — set VERSION to the release you want
VERSION=v0.17.0
TARGET=x86_64-unknown-linux-gnu
curl -LO https://github.com/achird-labs/rift/releases/download/$VERSION/rift-$VERSION-$TARGET.tar.gz
tar -xzf rift-$VERSION-$TARGET.tar.gz
sudo mv rift-$VERSION-$TARGET/bin/* /usr/local/bin/
rift --versionEach archive unpacks to a bin/ directory containing rift (the server), rift-lint, rift-tui,
and rift-verify.
Available platforms:
- Linux:
x86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu,x86_64-unknown-linux-musl,aarch64-unknown-linux-musl - macOS:
x86_64-apple-darwin,aarch64-apple-darwin - Windows:
x86_64-pc-windows-msvc
git clone https://github.com/achird-labs/rift.git
cd rift
cargo build --release
./target/release/rift-http-proxyFor Node.js projects, use the official rift-node SDK
(@rift-vs/rift on npm). It runs the engine three ways — embedded in-process (FFI via the
companion @rift-vs/rift-embedded package, no Docker), connected to any running admin endpoint, or
as a managed spawned binary — with a fluent DSL for stubs/predicates/responses/scenarios, plus
Vitest and Jest testkits. Requires Node.js >= 20, ESM-only, zero runtime dependencies:
npm install @rift-vs/riftimport{rift,imposter,onGet,okJson,times}from'@rift-vs/rift';awaitusingengine=awaitrift.embedded();// or rift.connect(url) / rift.spawn()constusers=awaitengine.create(imposter('users').stub(onGet('/api/users/1').willReturn(okJson({id: 1,name: 'Alice'}))));awaitfetch(`${users.url}/api/users/1`);awaitusers.verify(onGet('/api/users/1'),times(1));// throws with a diff on mismatchAlready on Mountebank, or migrating from the pre-monorepo @rift-vs/rift? The Mountebank-compatible
create() stays available as a permanent drop-in, so adopting the typed DSL above is incremental,
not a forced rewrite.
See the rift-node docs for the full feature surface.
For JVM projects, use the official rift-java SDK.
It runs the engine three ways — embedded in-process (Panama FFM, no Docker), connected to any
running admin endpoint, or as a managed spawned binary — with a fluent DSL plus JUnit 5, Spring,
and Testcontainers integrations. Available on Maven Central under io.github.achird-labs:
<dependency>
<groupId>io.github.achird-labs</groupId>
<artifactId>rift-java-core</artifactId>
<scope>test</scope>
</dependency>try (Riftrift = Rift.embedded()) { // or Rift.connect(uri) / Rift.spawn()Imposterusers = rift.create(
imposter("users").stub(onGet("/api/users/1").willReturn(okJson("{\"id\":1}"))));
// point your SUT at users.uri(), then assert:users.verify(onGet("/api/users/1"), times(1));
}See the rift-java docs for the full feature surface.
For Scala 3, use the official rift-scala SDK. It is effect-library-native — ZIO, Cats Effect 3 / FS2, or no effect system at all — over the same four transports (embedded, connect, spawn, container):
libraryDependencies +="io.github.achird-labs"%%"rift-scala-zio"%"0.1.4"%Testimportrift.dsl.*importrift.zio.Riftfor
users <-Rift.create(
imposter("users").record.stub(
get("/api/users/1").reply(ok.json("""{"id":1}"""))
)
)
_ <- callSut(users.uri) // point your SUT at users.uri
_ <- users.verify(get("/api/users/1"), 1)
yield ()
// .provideShared(Rift.embedded) — or Rift.connect(uri) / Rift.spawn() / Rift.container()It also ships a zio-bddMockControl adapter, certified
against zio-bdd's published conformance catalogue. See the
rift-scala docs.
For Go projects, use the official rift-go SDK. It runs the
engine three ways — embedded in-process, connected to any running admin endpoint, or as a managed
spawned binary — with a fluent DSL plus testing.T helpers.
The embedded transport loads the engine through
purego rather than cgo, so CGO_ENABLED=0 keeps
working: no C toolchain, no cross-compilation penalty, and nothing about a consumer's build
changes by depending on it.
go get github.com/achird-labs/rift-go
go run github.com/achird-labs/rift-go/cmd/rift-fetch@latest -version v0.17.0funcTestUserLookup(t*testing.T) {
users:=rifttest.Imposter(t, rift.NewImposter("users").
Stub(rift.OnGet("/api/users/1").
Return(rift.OKJSON(map[string]rift.JSON{"id": 1, "name": "Alice"}))))
callSUT(t, users.BaseURL()) // point your SUT at users.BaseURL()rifttest.AssertReceived(t, users, rift.OnGet("/api/users/1"), rift.Once())
}Assertion counting runs through the engine's own predicate evaluator, so xpath, jsonpath and
inject predicates mean the same thing in a verification as in a stub — and a failure reports the
nearest non-matching request with the clauses it failed. See the
rift-go docs.
Java, Scala, Node/TypeScript and Go are all officially supported, and all four replay the same conformance corpus so their DSLs stay in lockstep with the engine grammar. The Language SDKs section collects the install snippets, hello-worlds, transport matrix and version-compatibility table.
- Installation - Docker, binary, build from source
- Quick Start - Create your first imposter
- Node.js Integration - npm package for Node.js
- Language SDKs - Java, Scala, Node/TypeScript and Go, with the transport and version-compatibility matrices
- Java / JVM SDK - rift-java for JUnit 5, Spring, and Testcontainers
- Scala SDK - rift-scala for ZIO, Cats Effect, FS2, and zio-bdd
- Go SDK - rift-go for
testing.T, embedded via purego (no cgo) - Migration Guide - Using Rift with Mountebank configs
- Imposters - Mock server configuration
- Predicates - Request matching
- Responses - Response configuration
- Behaviors - wait, decorate, copy
- Proxy Mode - Record and replay
- Mountebank Format - JSON configuration
- Native Rift Format - YAML for advanced features
- CLI Reference - Command-line options
- Fault Injection - Chaos engineering
- Scripting - Rhai, JavaScript
- TLS/HTTPS - Secure connections
- Metrics - Prometheus integration
- TUI - Interactive terminal interface
- Docker - Container deployment
- Kubernetes - K8s patterns
- REST API - Admin API reference
- Performance - Benchmarks
{
"port": 4545,
"protocol": "http",
"name": "User Service",
"stubs": [
{
"predicates": [{ "equals": { "method": "GET", "path": "/users" } }],
"responses": [{
"is": {
"statusCode": 200,
"headers": { "Content-Type": "application/json" },
"body": [{ "id": 1, "name": "Alice" }]
}
}]
},
{
"predicates": [{
"and": [
{ "equals": { "method": "GET" } },
{ "matches": { "path": "/users/\\d+" } }
]
}],
"responses": [{
"is": { "statusCode": 200, "body": { "id": 1, "name": "Alice" } }
}]
}
]
}More examples in examples/.
Prometheus metrics on :9090/metrics:
curl http://localhost:9090/metricsMetrics include request counts, latency histograms, fault injection stats, and more.
Rift includes additional command-line tools. All tools are included when you install via Homebrew or download release binaries.
Manage imposters and stubs through an interactive terminal interface:
# If installed via Homebrew or release binary
rift-tui
# Connect to a different admin URL
rift-tui --admin-url http://localhost:2525Features:
- View and manage imposters with vim-style navigation (j/k)
- Create, edit, and delete stubs with JSON editor
- Generate curl commands for testing stubs
- Import/export imposter configurations
- Search and filter imposters and stubs
- Real-time metrics dashboard
Automatically test your imposters by generating requests from predicates:
rift-verify --show-curlValidate imposter configuration files before loading:
# If installed via Homebrew or release binary
rift-lint ./imposters/
# Via Docker (for CI/CD)
docker run --rm -v $(pwd):/imposters zainalpour/rift-lint .# Via cargo
cargo install rift-lint
rift-lint ./imposters/# Build
cargo build --release
# Run tests
cargo test --all
# Run with debug logging
RUST_LOG=debug ./target/release/rift-http-proxy
# Run benchmarks (Rift vs Mountebank; see tests/benchmark/README.md)cd tests/benchmark && python3 scripts/bench_direct.py --run-all \
--rift-bin ../../target/release/rift-http-proxy \
--mb-bin ~/bench-mb/node_modules/mountebank/bin/mbRift powers HTTP mocking in the following projects:
- zio-bdd — a Gherkin-style BDD testing framework for ZIO
- zio-openfeature — a ZIO-native wrapper around the OpenFeature Java SDK
Using Rift somewhere? Open a PR to add it here.
Contributions welcome! Please read our contributing guidelines and submit PRs.
Apache License 2.0 - see LICENSE for details.
- Mountebank - The original service virtualization tool that inspired Rift's API and configuration format

