World’s largest virtual agentic engineering & quality conference
A REST Assured tutorial for API testing in Java: Maven setup, runnable CRUD examples, JSON schema validation, auth setup, and the Java 17 change in version 6.

Sai Krishna
Author

Sushobhit Dua
Reviewer
Last Updated on: August 7, 2026
Overview
REST Assured is an open source Java library that tests REST APIs using a readable given/when/then chain. You declare the request, fire an HTTP method, and assert on the status code, headers, and JSON body in one statement, inside the same Maven or Gradle build that runs your unit tests.
Why Do Java Teams Choose REST Assured for API Testing?
How Do You Write Your First REST Assured Test?
Add the io.rest-assured:rest-assured dependency with test scope, set RestAssured.baseURI once, then chain given().when().get("/posts/1").then().statusCode(200). Every example in this tutorial runs against a public API, so you can paste them into a fresh project and watch them pass without standing up a server first.
What Changed in REST Assured 6.0?
Version 6.0.0 raised the minimum Java baseline to 17 and the Groovy baseline to 5.x, added Jackson 3 support, and moved json-path off GroovyShell. Version 6.0.1 then capped JSON number literals to stop a denial-of-service in JsonPath. Most tutorials still describe the 5.x world, so an upgrade fails for reasons they never mention.
A REST Assured suite that compiled happily on Java 11 stops building the moment you move to the current release. Nothing in your test code changed, and the error points at a class file version rather than an assertion.
The cause is a deliberate break. The official REST Assured changelog records that 6.0.0, released 12 December 2025, set the minimum Java baseline to 17 and the minimum Groovy baseline to 5.x. The current release is 6.0.1, published 10 July 2026, and the library is distributed under Apache-2.0.
This REST Assured tutorial covers setup, a full CRUD suite you can run against a public API, JSON and schema validation, authentication, and what version 6 changed. Every response shown below came from an actual run, not from a sample in the docs.
REST Assured is a Java library that describes itself as a Java DSL for easy testing of REST services. It wraps an HTTP client and an assertion layer in a single fluent chain, so a request and its verification live in one expression instead of being split across a client call, a parse step, and a set of assertions.
It supports POST, GET, PUT, DELETE, OPTIONS, PATCH, and HEAD, and it can specify and validate parameters, headers, cookies, and request or response bodies. Because tests are ordinary Java classes, they sit in source control beside the application, go through code review, and run in the same build as everything else.
If you are still deciding what to cover at the service layer before writing code, our guide to REST API testing works through the request types and status handling this tutorial assumes you already know.
Add the dependency at test scope. The Maven coordinates are io.rest-assured:rest-assured, and version 6.0.1 requires a JDK of 17 or newer.
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>6.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>6.0.1</version>
<scope>test</scope>
</dependency>Two static imports keep the test bodies short. Import the entry points and the Hamcrest matchers, then set a base URI once so every test writes only the path.
import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeAll;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
class PostApiTest {
@BeforeAll
static void setUp() {
RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
}
}Check your JDK before anything else. On Java 11 the dependency resolves and then fails at class load, which reads like a corrupt artifact rather than a version floor.
The chain has three stages, and keeping them separate is what makes REST Assured tests readable. The canonical form in the project README is short enough to memorise.
given().
param("key1", "value1").
param("key2", "value2").
when().
post("/somewhere").
then().
body(containsString("OK"));A test that skips given() entirely is valid when there is nothing to configure. Starting a chain at when() for a plain GET is idiomatic, not a shortcut.
The four tests below form a complete CRUD suite against a public API, so they run without a local server. Before writing the assertions we called each endpoint directly and recorded what it actually returns.
GET /posts/1 -> 200 application/json
{ "userId": 1, "id": 1, "title": "sunt aut facere repellat provident ...", "body": "quia et suscipit ..." }
POST /posts -> 201 application/json
{ "title": "rest assured", "body": "crud demo", "userId": 1, "id": 101 }
PUT /posts/1 -> 200 application/json
{ "id": 1, "title": "updated", "body": "updated body", "userId": 1 }
DELETE /posts/1 -> 200 application/json
{}Two details from that run change how the tests are written. The create returns 201 rather than 200, and the delete returns 200 with an empty object rather than 204. Assert what the API does, not what the HTTP conventions suggest it should do.
@Test
void getPostReturnsExpectedFields() {
given()
.pathParam("id", 1)
.when()
.get("/posts/{id}")
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("id", equalTo(1))
.body("userId", equalTo(1))
.body("title", not(emptyString()));
}Using pathParam rather than string concatenation keeps the template readable and lets REST Assured handle encoding. Asserting that title is non-empty, instead of pinning its exact text, keeps the test from breaking when seed data changes.
@Test
void createPostReturns201() {
String payload = """
{
"title": "rest assured",
"body": "crud demo",
"userId": 1
}
""";
given()
.contentType(ContentType.JSON)
.body(payload)
.when()
.post("/posts")
.then()
.statusCode(201)
.body("title", equalTo("rest assured"))
.body("id", notNullValue());
}The text block is a Java 15 feature and is safe to use given the Java 17 baseline. Assert that the generated id is present rather than equal to a fixed number, because the value the server assigns is not yours to predict.
@Test
void updatePostReplacesFields() {
Map<String, Object> payload = Map.of(
"id", 1,
"title", "updated",
"body", "updated body",
"userId", 1
);
given()
.contentType(ContentType.JSON)
.body(payload)
.when()
.put("/posts/1")
.then()
.statusCode(200)
.body("title", equalTo("updated"))
.body("body", equalTo("updated body"));
}Passing a Map instead of a JSON string lets REST Assured serialise the body through the object mapper on the classpath. That removes the quoting mistakes that hand-written JSON literals invite.
@Test
void deletePostSucceeds() {
when()
.delete("/posts/1")
.then()
.statusCode(200);
}No given() stage appears because there is nothing to configure. Against a real service, follow the delete with a GET that expects 404, since a delete returning success does not prove the record is gone.
Field assertions catch wrong values. They do not catch a field that quietly disappeared or changed type, which is the failure mode that breaks consumers. Schema validation covers that gap.
import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;
@Test
void postMatchesContract() {
when()
.get("/posts/1")
.then()
.statusCode(200)
.body(matchesJsonSchemaInClasspath("post-schema.json"));
}The schema lives in src/test/resources and describes the contract rather than the data. Requiring the fields and pinning their types is usually enough to catch drift.
{
"type": "object",
"required": ["id", "userId", "title", "body"],
"properties": {
"id": { "type": "integer" },
"userId": { "type": "integer" },
"title": { "type": "string" },
"body": { "type": "string" }
}
}For collections, JsonPath expressions handle aggregate checks in one line. Asserting body("size()", greaterThan(0)) on a list endpoint, or extracting with path("[0].id"), avoids looping in the test.
REST Assured has first class support for the common schemes, so authentication is part of the given() stage rather than manual header assembly.
// Basic, sent immediately instead of waiting for a 401 challenge
given().auth().preemptive().basic(username, password)
// Bearer token
given().auth().oauth2(accessToken)
// Any custom scheme
given().header("Authorization", "ApiKey " + apiKey)Prefer preemptive basic auth in tests. Without it REST Assured waits for a challenge, which doubles the request count and fails outright against servers that return 403 instead of 401.
Read credentials from environment variables, never from a committed file. A token in a test resource is a token in your git history, and rotating it later does not remove it.
Note: API tests prove the contract holds. They cannot tell you the screen consuming that response still renders. TestMu AI pairs both layers in one pipeline. Try TestMu AI free!
Version 6 is the first release line in years that breaks builds on purpose, and most tutorials still describe 5.x behaviour. These are the changes the changelog records for 6.0.0 and 6.0.1.
| Change | What it means for your suite |
|---|---|
| Java baseline raised to 17 | Builds on Java 11 or older fail at class load. Upgrade the JDK before upgrading the library, and text blocks become available in test code. |
| Groovy baseline raised to 5.x | A project pinning an older Groovy through Spock or a plugin hits a clash. Resolve it with an exclusion rather than by downgrading REST Assured. |
| json-path moved off GroovyShell | JsonPath evaluation is now pure Java, which fixed memory leaks in long running processes that used JsonPath heavily. |
| Jackson 3 support | Object mapping works when Jackson 3 is the only Jackson on the classpath, which matters on newer Spring Boot builds. |
| JSON number length capped (6.0.1) | Oversized numeric literals in untrusted JSON previously became arbitrarily large BigIntegers at O(n^2) CPU and heap cost. Tokens are now capped at 1000 characters. |
That last entry is a genuine denial-of-service fix, credited in the changelog to a security researcher who reported it privately. If your tests parse JSON you do not control, such as a third party sandbox or a fuzzing target, the cap is configurable.
// Default is 1000 characters; a negative value disables the check entirely
RestAssured.config = RestAssured.config()
.jsonConfig(JsonConfig.jsonConfig().numberLengthLimit(1000));REST Assured tests are ordinary JUnit or TestNG tests, so Maven runs them with no extra plugin. Pin the JDK to 17 or newer and the job is three lines.
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: API contract tests
run: mvn -B testA green API suite proves the contract holds. It says nothing about the screen consuming that response, which is where the next class of failure lives: a button wired to the wrong endpoint, a redirect that 404s, a form that never validates. The code is correct and the user-facing result is broken.
That second layer is what Kane CLI covers. It is a deterministic browser agent that drives real Chrome from a natural language objective and verifies the rendered result, so it gates the same pipeline immediately after the API stage.
npm install -g @testmuai/kane-cli
# Agent mode emits machine-readable output and needs no display server
kane-cli run "Open the orders page and confirm the latest order appears" --agent --headlessBoth stages gate on exit codes, so the pipeline logic stays uniform. Kane CLI returns 0 for a pass, 1 for a failed assertion, 2 for an error such as an auth failure, and 3 for a timeout or cancellation. The Kane CLI getting started documentation covers authentication and configuration.
Where REST Assured fits against other options is a separate decision, and our comparison of API testing tools covers the trade-offs. For broader service layer strategy, including parallel execution and backward compatibility, see our session write up on REST API automation strategies.
Start by checking your JDK, then add the 6.0.1 dependency and port one existing test to the given/when/then chain. The four CRUD examples above run against a public API, so you can confirm your setup works before pointing anything at your own service.
Once the suite is green in CI, the remaining risk moves up a layer to the interface consuming those endpoints. TestMu AI runs that verification in the same pipeline through Kane CLI, and the getting started documentation linked above walks through installation and authentication. If you are preparing for a role rather than a release, our REST API interview questions cover the same ground from the other side of the table.
Author
Sai Krishna is Director of Engineering at TestMu AI (formerly LambdaTest), where he leads agentic AI for quality engineering, building AI agents that autonomously drive mobile and conversational test automation. His current focus is Agent Testing and Model Context Protocol (MCP) support for mobile. He is a core contributor and member of the Appium open-source project and the creator of AppiumTestDistribution and appium-device-farm. With over 14 years of experience including more than 9 years at Thoughtworks as a Principal Consultant, he holds a BSc in Electronics and speaks regularly at TestMu and Appium Conf on Appium, mobile automation, and agentic AI in testing.
Reviewer
Sushobhit Dua is an Engineering Manager at TestMu AI (formerly LambdaTest), leading SmartUI, the visual regression and visual testing product. He manages the team that builds and ships SmartUI and maintains and cuts releases of the open-source SmartUI CLI. He works primarily in Core Java, Spring Boot, and Gradle, and is an AMCAT Certified Software Engineer. He brings over 10 years of software engineering experience, with earlier work as a Software Engineer at ecare Technology Labs. Sushobhit owns the SmartUI roadmap and the engineering decisions behind it.
Did you find this page helpful?
More Related Blogs
TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance