JMeter’s ramp-up period is actually a bit of a misnomer; it doesn’t guarantee a perfectly smooth, linear increase in load.

Let’s watch it in action. Imagine we’re testing an API endpoint /users. We want to simulate 100 users hitting this endpoint, but we don’t want them all to start at once. A sudden flood of 100 requests can overwhelm a system and give you misleading results. Instead, we want them to spread out over 60 seconds.

Here’s a simple JMeter test plan snippet:

<hashTree>
    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Ramp-Up Example" enabled="true">
        <stringProp name="TestPlan.comments"></stringProp>
        <boolProp name="TestPlan.functional_mode">false</boolProp>
        <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
        <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true">
            <collectionProp name="Arguments.arguments"/>
        </elementProp>
        <stringProp name="TestPlan.userComment"></stringProp>
    </TestPlan>
    <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" enabled="true">
            <boolProp name="LoopController.continue_forever">false</boolProp>
            <stringProp name="LoopController.loops">1</stringProp>
        </elementProp>
        <stringProp name="ThreadGroup.num_threads">100</stringProp>
        <stringProp name="ThreadGroup.ramp_time">60</stringProp>
        <boolProp name="ThreadGroup.scheduler">false</boolProp>
        <stringProp name="ThreadGroup.duration"></stringProp>
        <stringProp name="ThreadGroup.delay"></stringProp>
    </ThreadGroup>
    <hashTree>
        <HTTPSamplerProxy guiclass="HttpSamplerGui" testclass="HTTPSamplerProxy" testname="GET /users" enabled="true">
            <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HttpArgumentsPanel" testclass="Arguments" enabled="true">
                <collectionProp name="Arguments.arguments"/>
            </elementProp>
            <stringProp name="HTTPSamplerProxy.domain">localhost</stringProp>
            <stringProp name="HTTPSamplerProxy.port">8080</stringProp>
            <stringProp name="HTTPSamplerProxy.protocol">http</stringProp>
            <stringProp name="HTTPSamplerProxy.contentEncoding"></stringProp>
            <stringProp name="HTTPSamplerProxy.path">/users</stringProp>
            <stringProp name="HTTPSamplerProxy.method">GET</stringProp>
            <boolProp name="HTTPSamplerProxy.follow_redirects">true</boolProp>
            <boolProp name="HTTPSamplerProxy.use_keepalive">true</boolProp>
            <boolProp name="HTTPSamplerProxy.do_ூ_auth">false</boolProp>
            <stringProp name="HTTPSamplerProxy.authentication">basic</stringProp>
            <stringProp name="HTTPSamplerProxy.username"></stringProp>
            <stringProp name="HTTPSamplerProxy.password"></stringProp>
            <boolProp name="HTTPSamplerProxy.embedded_extractor">false</boolProp>
            <stringProp name="HTTPSamplerProxy.user_agent"></stringProp>
            <boolProp name="HTTPSamplerProxy.image_parser">false</boolProp>
            <stringProp name="HTTPSamplerProxy.ip_source_address"></stringProp>
            <stringProp name="HTTPSamplerProxy.host">localhost</stringProp>
            <int name="HTTPSamplerProxy.connect_timeout"></int>
            <int name="HTTPSamplerProxy.response_timeout"></int>
        </HTTPSamplerProxy>
        <hashTree/>
    </hashTree>
</hashTree>

In this setup, num_threads is 100, and ramp_time is 60. What this actually means is that JMeter will try to start all 100 threads over the course of 60 seconds. It calculates an interval: 60 seconds / 100 threads = 0.6 seconds per thread. So, it aims to start a new thread approximately every 0.6 seconds.

This mechanism is crucial for load testing realism. Instead of simulating a sudden "flash crowd" which might not reflect typical user behavior or system ramp-up capabilities, the ramp-up allows your application and its infrastructure to gradually accept the increased load. This helps identify performance bottlenecks that only appear under sustained, increasing pressure, rather than just at the absolute peak. It also prevents your testing tool from becoming the bottleneck by trying to spin up too many threads simultaneously, which can lead to inaccurate results.

The key takeaway is that the ramp-up isn’t about how many requests per second are sent during the ramp-up phase, but rather about how many threads are started. The rate of requests per second will naturally increase as more threads become active and begin executing their samplers. The actual request rate during the ramp-up is a function of the number of active threads and the response time of your system. If your system responds quickly, you’ll see a faster increase in requests per second than if it responds slowly.

Most people don’t realize that if you set a very short ramp-up time (e.g., 1 second for 100 threads), JMeter will attempt to start threads almost instantaneously. This can still create a significant spike, and depending on your JMeter setup (e.g., distributed testing, JVM settings), you might even see errors from JMeter itself as it struggles to manage thread creation. Conversely, a very long ramp-up time will spread the load so thinly that it might take a long time to reach your target concurrency, potentially making your test run much longer than intended.

The next thing you’ll grapple with is understanding how the ramp-up interacts with the duration and loops settings, and how to accurately measure the steady-state performance after the ramp-up is complete.

Want structured learning?

Take the full Jmeter course →