eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

1. Introduction

As web applications scale, they must efficiently handle increasing concurrent users and requests. Load testing is crucial in evaluating an application’s performance under varying traffic conditions, identifying potential bottlenecks, and ensuring reliability.

For instance, an e-commerce platform with a surge of users during a flash sale. Without proper load testing, performance bottlenecks could lead to slow response times or even downtime, affecting user experience and revenue.

The k6 framework is a powerful, open-source load-testing tool that bridges simplicity with sophisticated performance analysis. Designed to enable developers to create comprehensive performance test scenarios, combining JavaScript scripting with efficient execution capabilities.

In this tutorial, we’ll explore the complete process of setting up k6, conducting load tests, analyzing performance metrics, and adhering to best practices for ensuring optimal system performance.

2. Setting up K6 for Load Testing

To begin load testing with k6, we need to install the tool and set up a test script.

2.1. Installation

Getting started with k6 is straightforward. We can install it using our system’s package manager.

For instance, on macOS, we can run the following command:

$ brew install k6

Once the installation is complete, let’s verify it by checking the version:

$ k6 version

The output confirms the installation:

k6 v0.56.0 (go1.23.4, darwin/arm64)

2.2. Test Script

Let’s develop a basic load test script named test.js that demonstrates k6’s core capabilities:

import http from 'k6/http';
import { sleep, check } from 'k6';

// test configuration
export const options = {
  vus: 10, // simulate concurrent virtual users
  duration: '30s',
  
  thresholds: {
    http_req_duration: ['p(95)<500'], // 95% of requests must complete under 500ms
    http_req_failed: ['rate<0.01']    // less than 1% request failure rate
  }
};

// test scenario
export default function() {
  
  // simulate request
  const response = http.get('https://test-api.k6.io'); 
  
  // validate response
  check(response, {
    'status is 200': (r) => r.status === 200,
    'response time is acceptable': (r) => r.timings.duration < 500
  });
  
  // simulate user activity
  sleep(1);
}

In this script, k6 defines test configurations with 10 virtual users and a 30-second duration. It ensures that 95% of requests are completed in under 500ms, with a failure rate of less than 1%.

The script then defines test scenarios in which GET requests are sent to an API endpoint, confirming that the response status is 200 and verifying that the response time stays below 500ms.

Also, to maintain a steady and realistic load on the system, each virtual user pauses for 1 second between requests.

Let’s execute the test script:

k6 run test.js

Once run, the script captures key performance metrics, validates response conditions, and provides a comprehensive report of the load test. This report includes response times, request rates, and any potential failures.

3. Executing Load Tests

By successfully setting up k6 and executing our first basic load test, we laid the groundwork for comprehensive performance evaluation.

With our initial configuration in place, we can now explore more sophisticated testing scenarios that mimic real-world application usage.

3.1. Advanced Testing Scenarios

k6 allows for flexible configuration of test scenarios through the options object, making it easy to simulate real-world traffic patterns. For example, we can define ramp-up and ramp-down stages to mimic user behavior:

export const options = {
  stages: [
    { duration: '1m', target: 20 }, // ramp up to 20 users in 1 minute
    { duration: '3m', target: 20 }, // hold steady for 3 minutes
    { duration: '1m', target: 0 },  // ramp down to 0 users in 1 minute
  ],
};

This setup gradually increases the load, holds it steady for a specific period, and then gracefully decreases it, which helps test the application’s performance under realistic usage conditions.

3.2. Testing Multiple Endpoints

We can evaluate the performance of multiple API routes in a single script, ensuring comprehensive test coverage. Here’s an example:

export default function() {
  
  // simulate diverse user interactions
  const responses = http.batch([
    ['GET', 'https://test-api.k6.io/public/crocodiles/'],
    ['POST', 'https://test-api.k6.io/auth/basic/login/', JSON.stringify({username:'test',password:'1234'})]
  ]);

  // validate multiple endpoint responses
  check(responses[0], { 'Crocodiles loaded': (r) => r.status === 200 });
  check(responses[1], { 'Login done': (r) => r.status === 200 });
}

The script demonstrates a multi-endpoint load test by simultaneously executing a batch of HTTP requests (fetching crocodiles and login) and performing synchronous response validation to ensure each endpoint returns a successful HTTP 200 status code.

3.3. Built-in Metrics

k6 automatically provides a range of insightful metrics, such as:

  • http_req_duration: total request duration, including DNS lookup, connection, and response time
  • http_reqs: total number of HTTP requests made
  • http_req_failed: percentage of failed requests
  • vus: current number of virtual users
  • iterations: total number of script iterations executed

These metrics offer high-level and granular insights, setting the foundation of our performance analysis.

3.4. Custom Metrics

Custom metrics are a powerful feature in k6, enabling us to monitor specific aspects of application performance. For instance, we can track failed login attempts using a custom counter:

import { Counter } from 'k6/metrics';

let loginFailures = new Counter('login_failures');

export default function () {
  let res = http.post('https://test-api.k6.io/auth/basic/login/', { 
    username: 'test',
    password: 'wrong_password'
  });
  if (res.status !== 200) {
    loginFailures.add(1); // Increment the counter for failed logins
  }
}

Therefore, the features of k6 such as customizable scenarios, multi-endpoint testing, and detailed metric tracking equip us with the tools to evaluate our application performance.

4. Load Test Results

Analyzing load test results is vital for understanding application performance, identifying bottlenecks, and ensuring reliability.

4.1. Export Results

k6 allows us to export test results in various formats for further analysis. For instance, we can save the output in JSON format:

$ k6 run --out json=results.json test.js

This enables us to integrate k6 data with visualization tools like Grafana or to share detailed test reports.

A sample of k6 terminal output might look like this:

     execution: local
        script: test.js
        output: json (results.json)
     scenarios: (100.00%) 1 scenario, 10 max VUs, 1m0s max duration (incl. graceful stop):
              * default: 10 looping VUs for 30s (gracefulStop: 30s)
     ✓ status is 200
     ✓ response time is acceptable

   ✓ http_req_duration..............: avg=109.59ms min=104.52ms med=108.86ms max=190.22ms p(90)=113.03ms p(95)=114.66ms
   ✓ http_req_failed................: 0.00%   0 out of 270
     vus............................: 10      min=10         max=10
     vus_max........................: 10      min=10         max=10

running (0m30.3s), 00/10 VUs, 270 complete and 0 interrupted iterations
default ✓ [======================================] 10 VUs  30s

These metrics provide a clear overview of system performance, highlighting areas that may require optimization. By combining built-in metrics, detailed analysis, and exported reports, we can ensure a thorough evaluation of our application’s behavior under load.

4.2. Results Analysis

When analyzing load test results, let’s start by focusing on the response time analysis, which includes:

  • Monitoring p95 and p99 percentiles to measure how the system performs for 95% and 99% of requests
  • Tracking maximum response times to identify potential outliers that might impact user experience
  • Looking for patterns of degradation, such as increasing response times under sustained load

Next, let’s consider error rate analysis, where we:

  • Pay attention to the percentage of failed requests to assess overall reliability
  • Analyze the types and patterns of errors, such as timeouts, 4xx, or 5xx responses
  • Observe how the system behaves under load, particularly when it approaches its performance limits

Lastly, let’s examine resource utilization, which involves:

  • Monitoring CPU usage patterns to detect over-utilization or bottlenecks
  • Keeping an eye on memory consumption, particularly during high loads
  • Tracking network throughput to identify constraints in bandwidth or API capacity

5. Best Practices and Common Pitfalls

Once results are in hand, following best practices and avoiding common pitfalls is essential for achieving meaningful and reliable outcomes.

5.1. Best Practices

To ensure effective load testing, we should keep the following in mind:

  • Start small – gradually increase the load to avoid overwhelming the system and better understand application behavior under incremental stress
  • Test realistic scenarios – simulate real-world traffic patterns, including ramp-up and ramp-down phases, to ensure test results reflect user behavior
  • Automate tests – incorporate k6 tests into CI/CD pipelines for continuous performance monitoring and early issue detection as the application evolves
  • Use tags for categorization – add tags to HTTP requests to organize and analyze results more effectively

5.2. Common Pitfalls

We need to avoid these common pitfalls to ensure accurate test outcomes:

  • Overloading the test system – ensure the machine running k6 has adequate resources to handle the load, as underpowered systems can produce inaccurate results
  • Unrealistic configurations – configure test parameters that reflect real-world usage, avoiding excessive virtual users or unrealistic request patterns
  • Neglecting post-test analysis: without thorough result dissection, performance pitfalls remain hidden, and optimization potential is untapped

6. Conclusion

In this article, we discussed the setup of k6, the creation of realistic test scenarios, result analysis, and best practices to achieve reliable load-testing outcomes.

Load testing isn’t just about stress-testing an application – it’s about understanding its behavior and ensuring it can handle real-world traffic.

With k6’s intuitive scripting, built-in metrics, and flexible configurations, we can simulate realistic traffic patterns, accurately assess performance, and prepare our systems to scale effectively. We must start small, experiment with advanced scenarios, and refine our tests to adapt to evolving traffic demands.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments