A JMeter spike test doesn’t just simulate traffic; it reveals how your system buckles under sudden, massive load, often exposing weaknesses a gradual ramp-up would miss.

Let’s see this in action. Imagine you’re testing an e-commerce site’s checkout API. A normal test might gradually increase users from 100 to 1000 over 10 minutes. A spike test, however, would keep users at 50 for 5 minutes, then instantly jump to 5000 users for 30 seconds, before dropping back to 50. This sudden influx is what we want to simulate.

Here’s a basic JMeter test plan structure to achieve this:

<hashTree>
    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Spike Test Plan" enabled="true">
        <stringProp name="TestPlan.comments"></stringProp>
        <boolProp name="TestPlan.functional_mode">false</boolProp>
        <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
        <elementProp name="TestPlan.userDefinedVariables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true">
            <collectionProp name="Arguments.arguments"/>
        </elementProp>
        <stringProp name="TestPlan.userDefinedMacros"></stringProp>
        <stringProp name="TestPlan.userDefinedFunctions"></stringProp>
        <boolProp name="TestPlan.encoding">false</boolProp>
        <stringProp name="TestPlan.directory"></stringProp>
    </TestPlan>
    <hashTree>
        <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Spike Thread Group" enabled="true">
            <stringProp name="ThreadGroup.on_thread_group_start">start</stringProp>
            <stringProp name="ThreadGroup.on_thread_group_end">stop</stringProp>
            <boolProp name="ThreadGroup.describe_threads">false</boolProp>
            <stringProp name="ThreadGroup.num_threads">50</stringProp>
            <stringProp name="ThreadGroup.ramp_time">0</stringProp>
            <longProp name="ThreadGroup.duration">300</longProp>
            <longProp name="ThreadGroup.delay">0</longProp>
            <boolProp name="ThreadGroup.scheduler">true</boolProp>
            <stringProp name="ThreadGroup.period">300</stringProp>
            <stringProp name="ThreadGroup.targetThreads">5000</stringProp>
        </ThreadGroup>
        <hashTree>
            <HTTPSamplerProxy guiclass="HttpSamplerGui" testclass="HTTPSamplerProxy" testname="Checkout API Request" enabled="true">
                <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true">
                    <collectionProp name="Arguments.arguments"/>
                </elementProp>
                <stringProp name="HTTPSamplerProxy.domain">your-ecommerce-site.com</stringProp>
                <stringProp name="HTTPSamplerProxy.port">443</stringProp>
                <stringProp name="HTTPSamplerProxy.protocol">https</stringProp>
                <stringProp name="HTTPSamplerProxy.contentEncoding"></stringProp>
                <stringProp name="HTTPSamplerProxy.path">/api/checkout</stringProp>
                <stringProp name="HTTPSamplerProxy.method">POST</stringProp>
                <boolProp name="HTTPSamplerProxy.follow_redirects">true</boolProp>
                <boolProp name="HTTPSamplerProxy.use_keep_alive">true</boolProp>
                <boolProp name="HTTPSamplerProxy.do_not_parse_responses">false</boolProp>
                <boolProp name="HTTPSamplerProxy.image_parser">false</boolProp>
                <stringProp name="HTTPSamplerProxy.embedded_url_re"></stringProp>
                <stringProp name="HTTPSamplerProxy.connect_timeout"></stringProp>
                <stringProp name="HTTPSamplerProxy.response_timeout"></stringProp>
            </HTTPSamplerProxy>
            <hashTree/>
            <ResultCollector guiclass="ViewResultsTreeGui" testclass="ResultCollector" testname="View Results Tree" enabled="true">
                <boolProp name="isSaveConfigEnabled">false</boolProp>
                <stringProp name="filename"></stringProp>
            </ResultCollector>
            <hashTree/>
        </hashTree>
    </hashTree>
</hashTree>

The key here is the ThreadGroup configuration, specifically the ThreadGroup.scheduler and its associated properties.

  • ThreadGroup.num_threads: This is your baseline load. Set it to a low number, say 50, representing normal traffic.
  • ThreadGroup.ramp_time: Set this to 0. We want the initial load to be instant.
  • ThreadGroup.scheduler: Set this to true. This enables the scheduling of threads over time.
  • ThreadGroup.duration: This defines the total duration of the test in seconds. Let’s set it to 300 (5 minutes).
  • ThreadGroup.period: This defines the interval (in seconds) at which JMeter checks the scheduler. Set it to 300, matching the duration for a single "phase."
  • ThreadGroup.targetThreads: This is the magic for spikes. Set it to your peak load, e.g., 5000.

With this setup, JMeter will start with 50 threads. After 300 seconds (5 minutes), it will check the scheduler. Since targetThreads is set to 5000 and the current thread count is 50, it will attempt to ramp up to 5000 threads immediately (because ThreadGroup.ramp_time is 0 for the initial load, and JMeter’s scheduler aims for the target as quickly as possible when there’s a discrepancy). The test will then run for its total duration of 300 seconds. After this 5-minute mark, the targetThreads would ideally drop back down if we configured a more complex scheduler, but for a single spike, the duration of the thread group itself will eventually end the test.

To simulate multiple spikes, you’d use the "Ultimate Thread Group" plugin, which offers much more granular control over different phases of ramp-up, hold, and ramp-down. You can define multiple schedules within a single thread group: start with 50 users for 300 seconds, then jump to 5000 users for 30 seconds, then back to 50 users for 60 seconds, and so on.

The core problem spike testing solves is identifying resource exhaustion points that aren’t apparent under steady load. This could be a database connection pool that gets overwhelmed by a sudden rush of queries, a web server’s thread pool that can’t handle a rapid influx of new connections, or even external API rate limits that get hit hard and fast.

The data you’re looking for in your results (using a Listener like "View Results in Table" or "Aggregate Report") are not just average response times, but the distribution of response times during the spike. Look for a significant increase in 90th and 95th percentile response times, and critically, a rise in error rates (e.g., HTTP 5xx errors). If your error rate spikes to 100% during the peak load, your system is not handling the sudden burst.

A subtle but critical aspect of spike testing configuration is understanding how JMeter’s scheduler interacts with the ThreadGroup.ramp_time for subsequent changes. While the initial ramp-up to your baseline num_threads might be immediate if ThreadGroup.ramp_time is 0, the ramp-up to your targetThreads during a scheduled spike will also attempt to be as immediate as possible. If you want a gradual ramp-up during the spike, you’d need to configure the Ultimate Thread Group or a more complex setup.

After successfully handling a spike to 5000 users, your next challenge will be testing the system’s recovery and its ability to return to normal performance levels once the load subsides.

Want structured learning?

Take the full Jmeter course →