Open vs. Closed Injection Profiles in Gatling: What Your Load Test Actually Proves
Choosing between Gatling's open and closed injection profiles changes whether your test measures system capacity or user experience under contention. Here's how to pick the right one and avoid hiding saturation.
04 Sept 2025, 07:42 UTC

The capacity test that wasn't
Your team runs a load test, sees green response times, and ships. Production falls over an hour later. The scenario is common, and often the root cause is not the system under test but the workload model used to measure it. In Gatling, the injection profile you attach to a scenario in the setUp block determines whether you are measuring maximum system capacity or user experience under contention. Confusing the two can hide the exact saturation point you were trying to find.
Two workload models, two different questions
Gatling broadly separates injection profiles into two families: open and closed.
An open workload—for example, constantUsersPerSec—generates new virtual users at a target arrival rate regardless of how long responses take. If the system under test slows down, the load generator keeps sending requests. This model answers the question: how much traffic can this system sustain before it breaks?
A closed workload—for example, atOnceUsers or rampUsers—maintains a fixed number of virtual users. Each user sends a request, waits for the response, and only then loops back to send the next one. If the system slows, users spend more time waiting, so the total request rate naturally drops. This model answers a different question: what does a bounded population of users experience when they contend for the same resource?
Why the distinction matters
In an open model, concurrency is an output. In a closed model, concurrency is an input. That single difference changes what your HTML report proves.
The closed-model trap in capacity testing
A frequent pitfall is using a closed profile to validate capacity. Imagine you set atOnceUsers(200) against an API and watch the report. As latency climbs, the closed model throttles its own offered load: because every virtual user is blocked waiting for a slow response, fewer requests are issued per second. The throughput chart flattens or declines, and it can look like the system has reached a stable plateau. In reality, saturation may be just around the corner, but the closed model has quietly stopped pushing hard enough to expose it.
An open profile does not have this governor. With constantUsersPerSec(200), the arrival rate stays fixed even when responses crawl. If the system cannot keep up, queues build, latency spikes, and errors surface. That is the signal you need for a capacity test.
When closed models are the right tool
Closed workloads are not wrong; they answer a different question. If you are modeling a known user base—say, 300 employees using an internal dashboard during a shift change—a closed model is more faithful. Those users do not spawn new sessions when the dashboard is slow; they wait. Queueing behavior, lock contention, and connection-pool exhaustion are all visible under a closed model precisely because the user count stays fixed while wait times grow.
It is tempting to try to turn an open workload into a closed one by adding throttling or pacing. In Gatling, throttle and pacing constrain the request rate, but they cap how fast requests are sent, not how many virtual users are in flight. They do not convert an open model into a closed one.
A worked comparison
The following simulation wires the same scenario to both profile types. Run it against a local HTTP service you control, then compare the requests-per-second and response-time charts in the Gatling HTML report.
import io.gatling.javaapi.core.*;
import io.gatling.javaapi.http.*;
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;
public class InjectionComparison extends Simulation {
HttpProtocolBuilder http = http.baseUrl("http://localhost:8080");
ScenarioBuilder scn = scenario("Basic")
.exec(http("Home").get("/"));
{
setUp(
// Open: fixed arrival rate, concurrency is an output
scn.injectOpen(constantUsersPerSec(50).during(2.minutes))
).protocols(http);
// Closed: fixed concurrency, throughput is an output
// scn.injectClosed(rampUsers(50).during(2.minutes))
}
}
Run each variant separately. In the open run, check whether throughput tracks the injected 50 requests per second or degrades as latency rises. In the closed run, watch how throughput falls when the service slows. If the closed run shows flat throughput and acceptable percentiles while the open run shows errors or queueing, you have found a saturation point that the closed model concealed.
Where to run this: locally or in a CI job with network access to your test service. No special Gatling permissions are required beyond the ability to execute the simulation. The main risk is that an aggressive open profile can saturate your load generator before the system under test; start with a low rate and increase it.
Limitations and verification
- Load generator limits: An open model can overwhelm a single Gatling node if the target rate exceeds what the machine can sustain. Monitor the injector's own CPU and network before blaming the target.
- Thundering herd:
atOnceUsersstarts all virtual users simultaneously.rampUsersis usually more realistic, but both assume users never abandon a slow session. - Version drift: Injection profile names and DSL syntax have changed across Gatling major versions. Confirm exact method names against the official injection DSL reference for your version before relying on this example.
A practical way to check the result is to run a short simulation with both constantUsersPerSec and atOnceUsers against a local service and compare the requests-per-second charts in the HTML report. Inspect response-time percentiles alongside throughput: if throughput tracks the injection rate, you are seeing an open workload; if it degrades as latency rises, you are seeing a closed one.
Pick the profile that matches the question
If you need to know where the system breaks, use an open profile and let arrival rate stay fixed. If you need to know what a fixed group of users experiences under contention, use a closed profile and watch throughput fall as latency climbs. The injection profile is not a detail; it is the hypothesis your test is built to prove.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.