World’s largest virtual agentic engineering & quality conference
A hands-on guide to REST API automation: REST Assured in Java, choosing a framework by language, parallel execution, and testing AI and LLM endpoints.
Ruchira Shukla
Author
Srinivasan Sekar
Reviewer
Last Updated on: July 16, 2026
REST API testing is a crucial aspect of software development that ensures web application functionality, reliability, and security. However, testing beyond the surface requires advanced strategies and techniques to evaluate complex software components and stimulate external services.
This guide covers REST API automation end to end: writing your first automated checks with REST Assured, picking a framework that matches your team’s language, running suites in parallel without making them flaky, and testing the newer class of AI and LLM-backed endpoints whose responses change on every call. The strategies here draw on a TestMu AI session by Julio de Lima, Principal QA Engineer at Capco, whose bio and full talk are at the end of this article.
Thorough REST API testing encompasses more than just functional validation; it extends to guaranteeing the resilience and longevity of your APIs. Here are the areas that matter once you test a REST API beyond basic functionality:

REST Assured is an open source Java library that removes the HTTP client boilerplate from API tests. Instead of assembling a request, executing it, parsing the response body, and then asserting on it across twenty lines of code, you describe the whole interaction as a single readable chain. It runs inside TestNG or JUnit, so your API tests live in the same suite and the same CI pipeline as everything else.
Start by adding the dependency to your pom.xml. Pair it with a test runner - TestNG in this example - and the Hamcrest matchers that REST Assured uses for assertions.
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>REST Assured borrows the Given-When-Then structure from BDD, and each keyword owns one part of the request lifecycle:
get(), post(), put(), patch(), or delete(). This is where the call actually leaves your machine.data.id.The example below covers both a GET and a POST against JSONPlaceholder, a free public test API, and asserts on the three things worth checking on almost every endpoint: the status code, the response time, and the shape of the JSON body.
import io.restassured.RestAssured;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import java.util.concurrent.TimeUnit;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.notNullValue;
public class UsersApiTest {
@BeforeClass
public void setup() {
RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
}
@Test
public void getSingleUserReturnsExpectedPayload() {
given()
.accept("application/json")
.when()
.get("/users/2")
.then()
.log().ifValidationFails()
.statusCode(200)
.time(lessThan(2L), TimeUnit.SECONDS)
.body("id", equalTo(2))
.body("email", notNullValue());
}
@Test
public void createPostReturns201WithGeneratedId() {
String payload = "{ \"title\": \"foo\", \"body\": \"bar\", \"userId\": 1 }";
given()
.contentType("application/json")
.body(payload)
.when()
.post("/posts")
.then()
.statusCode(201)
.body("title", equalTo("foo"))
.body("userId", equalTo(1))
.body("id", notNullValue());
}
}A few details in that snippet earn their place. .log().ifValidationFails() prints the full request and response only when an assertion fails, which keeps passing runs quiet but gives you everything you need to debug a red build. .time(lessThan(2L), TimeUnit.SECONDS) turns response time into a first-class assertion rather than something you notice later in a dashboard. And asserting email with notNullValue() instead of a hardcoded address is deliberate - pinning tests to fixture data that someone else can change is one of the most common sources of false failures.
Once these pass consistently, the natural next step is to extract the base URI and credentials into configuration, and to move repeated given() setup into a shared RequestSpecification so each test only expresses what makes it different.
REST Assured is the default answer for Java teams, but it is not the only sensible choice, and picking a framework in a language nobody on the team writes is a reliable way to end up with an unmaintained suite. The strongest signal is usually what your team already uses: the framework that matches your application’s stack is the one your developers will actually contribute tests to.
| Ecosystem | Framework | Best For | Assertion Style |
|---|---|---|---|
| Java | REST Assured with TestNG or JUnit | Teams already running Java or Selenium suites who want API and UI tests in one pipeline. | BDD given() / when() / then() chain with Hamcrest matchers. |
| Java (low-code) | Karate DSL | Testers who want API tests in Gherkin without writing Java. Built-in JSON assertions and parallel runner. | Gherkin feature files with match expressions. |
| Python | requests with pytest | Data-heavy validation, quick scripting, and CI glue. The lightest setup on this list. | Plain assert statements with pytest fixtures and parametrize. |
| JavaScript / TypeScript | Playwright APIRequestContext | Teams sharing one runner across UI and API tests, and reusing auth state between them. | expect() matchers with built-in retry. |
| JavaScript / TypeScript | SuperTest with Jest or Mocha | Testing Node and Express services in-process, without binding a real port. | expect() chained directly onto the request. |
| Low-code / GUI | Postman with Newman CLI | Exploratory API checks that graduate into CI. Newman CLI runs exported collections on a build agent. | Chai-style pm.test() assertions. |
One question comes up often enough to answer directly: can you automate a REST API using Selenium? Not meaningfully. Selenium is a browser automation library - it drives the DOM and has no HTTP client for asserting status codes, headers, or JSON bodies. You can call an API from inside a Selenium test using REST Assured or Java’s HttpClient, and doing so is good practice: seed your test data over the API in seconds rather than clicking through a signup form, then let Selenium assert only the UI behaviour that genuinely needs a browser.
API tests are fast individually and slow in aggregate - a suite of 500 endpoints at 300ms each still costs you two and a half minutes of pure waiting, and most of that is network latency doing nothing. Parallel execution is the obvious fix, and it is also where stable suites go to die. The failure mode is almost never the runner configuration. It is that tests which passed in a fixed sequential order were quietly depending on that order.
Get the isolation right before you touch the thread count.
ThreadLocal so each thread gets its own copy.Once the suite is genuinely parallel-safe, the ceiling shifts from your test design to the single machine running it. TestMu AI's HyperExecute test orchestration cloud distributes a suite across just-in-time infrastructure and runs it up to 70% faster than a traditional grid, which matters most for the large data-driven API suites that saturate a local thread pool.
TestNG controls parallelism from testng.xml. The parallel attribute decides what gets its own thread - methods runs each @Test method concurrently, while classes keeps methods within a class sequential and parallelises across classes, which is the safer starting point if your tests share class-level setup.
<?xml version="1.0" encoding="UTF-8"?>
<suite name="api-suite" parallel="methods" thread-count="10">
<test name="rest-api-tests">
<classes>
<class name="com.example.api.UsersApiTest"/>
</classes>
</test>
</suite>This is also the answer to a question that gets asked a lot: how do you run 100 test cases in parallel in TestNG? Set parallel="methods" and thread-count="100". Worth understanding, though, is that thread-count is a ceiling on a reused thread pool, not a promise of 100 simultaneous connections - and 100 threads hammering one service is usually a worse idea than it sounds. If your cases come from a @DataProvider, mark it @DataProvider(parallel = true) and tune data-provider-thread-count, which TestNG caps separately from the suite thread count and which catches people out when their data-driven tests stubbornly refuse to speed up.
JUnit 5 keeps parallel execution switched off by default and reads its settings from junit-platform.properties on the test classpath.
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.config.strategy = fixed
junit.jupiter.execution.parallel.config.fixed.parallelism = 10When a specific class genuinely cannot run concurrently - it mutates a global setting or a shared account - annotate it with @Execution(ExecutionMode.SAME_THREAD) rather than turning parallelism off for the entire suite. Use @ResourceLock when several tests contend for one named resource and you want JUnit to serialise just those.
A growing share of the endpoints QA teams are handed now sit in front of a language model, and they break the assumption every traditional API test rests on: that the same request produces the same response. LLM endpoints are non-deterministic. Send an identical prompt twice and you get two differently worded answers, both correct. An equalTo() assertion against the response text will pass on Tuesday and fail on Wednesday, and the test will be deleted within a month for being flaky.
The way through is to separate what is still deterministic from what is not, and to test each with the right tool.
The mental shift is from asserting equality to asserting acceptability within a tolerance - closer to how you would test a performance budget than a pure function.
Standing up this evaluation harness in-house is real work, which is why it is increasingly delivered as a product. TestMu AI's Agent Testing platform runs an agent under evaluation through 15+ specialized AI evaluators, including a hallucination detector, a bias detector, and a security researcher that probes prompt injection, then rolls their verdicts into a Green, Yellow, or Red production-readiness score. It is the same non-deterministic-output problem described above, handled as a managed evaluation rather than hand-rolled assertions. For the automation-framework side of the same shift, see AI API testing, which layers AI-driven test generation and self-healing checks that adapt when a schema or contract changes between releases.
Note: Test AI agents and LLM-backed APIs for hallucination, bias, and prompt injection with TestMu AI. Start testing free
Backward compatibility means your APIs keep working with older versions of the software that consume them. It matters because you rarely control every client: a mobile app that users have not updated, a partner integration, or an internal service on an older release can all break the moment you change a response shape. Adding a field is usually safe. Renaming one, removing one, tightening a validation rule, or changing a status code is not. The practical safeguard is to version your endpoints and keep a suite of contract tests for each supported version running against every build, so a breaking change fails in CI rather than in a customer’s integration.
REST APIs are designed to follow a specific architectural style, and adhering to it is what makes an API predictable enough to automate against. The principles that matter most in testing are statelessness, caching, and a uniform interface. Statelessness means every request carries everything needed to serve it, which is precisely why REST APIs parallelise well - if a request depends on server-side session state left behind by a previous one, concurrent tests will interfere with each other. Caching affects whether a repeated request even reaches your service. A uniform interface means the same HTTP verbs and status codes mean the same things across every endpoint, so your assertions stay consistent instead of special-cased per route.
Most REST APIs authenticate with a bearer token, and a JSON Web Token carries three parts: a header naming the signing algorithm, a payload of claims such as the subject, expiry, and scopes, and a signature over both. Testing tokens means more than checking that a valid one returns 200. The cases that find real bugs are the negative ones - an expired token, a token signed with the wrong key, one with its payload edited but the original signature attached, one with the algorithm switched to none, and one carrying a scope the endpoint should reject. Each should return 401 or 403, and none should return data.
Validation is where most API tests are weaker than they look. Asserting a 200 status code proves the request did not error; it proves nothing about the payload. A complete assertion covers the status code, the response schema, the types of each field, enum values, and the boundaries - empty arrays, nulls in optional fields, maximum string lengths, and numbers at the edge of their accepted range. JSON Schema validation is the efficient way to do this: rather than hand-writing an assertion per field, validate the whole body against a schema in one call, which also catches unexpected extra fields that a field-by-field check would miss entirely.
Your API almost certainly depends on services you do not control - a payment provider, an identity provider, a partner feed. Testing against the real thing makes your suite slow, rate-limited, and red whenever someone else has an outage. Mock services solve this: a tool such as WireMock stands in for the dependency and returns whatever you tell it to. The real value is not speed but reach. You can finally test the paths that matter and cannot be triggered on demand against a live system - a timeout, a 500, a malformed payload, a slow response that trips your circuit breaker. Those are the failure modes that take production down, and a mock is the only practical way to exercise them.

REST API automation rewards teams that start concrete and then harden. Get a handful of REST Assured checks passing against a real endpoint, asserting the status code, the response time, and the shape of the body rather than just a 200. Pick the framework your team will actually maintain - REST Assured if you write Java, requests and pytest if you write Python, Playwright or SuperTest if you live in JavaScript, Postman and Newman CLI if collections are already how your team works.
From there, the work is mostly about isolation and honesty. Tests that generate their own data and share no mutable state are the ones that survive being run in parallel. Mocked dependencies are what let you test the timeouts and 500s that actually take systems down. And for LLM-backed endpoints, accepting that the output is non-deterministic - asserting the schema strictly and the prose semantically - is what keeps those tests from being deleted for flakiness within a month. Testing beyond the surface is critical for ensuring web applications’ functionality, reliability, and security.
To run these suites without maintaining grid infrastructure yourself, TestMu AI's test automation cloud runs Selenium, Cypress, and Playwright alongside your API checks in one pipeline, and the HyperExecute documentation walks through wiring a parallel run into CI.
Julio de Lima is a seasoned software testing professional with more than a decade of experience in the industry. He serves as a principal engineer for Capco, where he plays a pivotal role in shaping robust and sustainable test strategies for financial institutions. Julio is also a speaker and trainer, sharing his expertise in conferences and workshops worldwide, and has trained thousands of testers through his Portuguese-language courses.
Watch the full TestMu AI session below, in which Julio walks through advanced REST API testing techniques including backward compatibility, token validation, and stimulating external services during testing.
If you couldn’t catch all the sessions live, don’t worry! You can access the recordings at your convenience by visiting the TestMu AI YouTube Channel.
Author
Ruchira Shukla is a Lead SDET with 13+ years of experience in software testing, QA automation, and delivery leadership across fintech and enterprise projects. She specializes in automation using Selenium, Appium, REST Assured, Cucumber, and TestNG, with strong expertise in Java-based testing frameworks. Ruchira is ISTQB Certified, has led global QA teams, and has authored articles on modern Selenium practices.
Reviewer
Srinivasan Sekar is Director of Engineering at TestMu AI (formerly LambdaTest), where he leads engineering and open-source initiatives behind the Selenium and Appium automation grid and owns TestMu AI's MCP Server. A committer to Appium and a contributor to Selenium, WebdriverIO, Taiko, and AppiumTestDistribution, he brings over 15 years of experience in quality engineering and open-source technologies. He is the author of the Apress book 'The MCP Standard: A Developer's Guide to Building Universal AI Tools with the Model Context Protocol,' a Certified Kubernetes and Cloud Native Associate, and an international conference speaker. Before TestMu AI he spent over eight years at Thoughtworks as a Principal Consultant and Quality Architect. Srinivasan holds a B.Tech in Information Technology from Anna University.
Did you find this page helpful?
More Related Blogs
TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance