REST Assured Tutorial: Getting Started with Java API Testing

REST Assured Tutorial: Getting Started with Java API Testing

Testing REST APIs in Java has a reputation for being verbose. You set up an HTTP client, construct requests, execute them, parse the response, and then write assertions against the parsed data — often 20+ lines for a simple GET test. REST Assured changes this. It provides a fluent, BDD-style API that makes API tests readable and concise.

This tutorial walks through setting up REST Assured and writing your first API tests from scratch.

What Is REST Assured?

REST Assured is a Java library for testing and validating REST services. It sits on top of Apache HttpClient and provides a domain-specific language (DSL) for writing HTTP tests that read almost like English:

given()
    .baseUri("https://api.example.com")
    .header("Authorization", "Bearer " + token)
.when()
    .get("/users/42")
.then()
    .statusCode(200)
    .body("name", equalTo("Alice"))
    .body("email", containsString("@example.com"));

That's a complete test. No response parsing, no manual assertion construction. REST Assured handles the HTTP layer and provides Hamcrest matchers for assertions.

Why Choose REST Assured?

REST Assured is the dominant HTTP testing library in the Java ecosystem for good reasons:

  • Mature and stable — over a decade of production use
  • BDD syntax — Given/When/Then maps naturally to test scenarios
  • Rich matchers — Hamcrest integration for flexible assertions
  • JSON/XML support — built-in path extraction for both formats
  • Schema validation — validate responses against JSON Schema
  • Spring Boot integration — first-class support for testing Spring applications
  • TestNG and JUnit support — works with both major Java test runners
  • Authentication helpers — OAuth 2.0, JWT, Basic Auth, and more built-in

Project Setup

Maven

Add REST Assured to your pom.xml:

<dependencies>
    <!-- REST Assured core -->
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>rest-assured</artifactId>
        <version>5.4.0</version>
        <scope>test</scope>
    </dependency>

    <!-- JSON path support -->
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>json-path</artifactId>
        <version>5.4.0</version>
        <scope>test</scope>
    </dependency>

    <!-- XML path support (optional) -->
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>xml-path</artifactId>
        <version>5.4.0</version>
        <scope>test</scope>
    </dependency>

    <!-- JUnit 5 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.10.2</version>
        <scope>test</scope>
    </dependency>

    <!-- Hamcrest (for matchers) -->
    <dependency>
        <groupId>org.hamcrest</groupId>
        <artifactId>hamcrest</artifactId>
        <version>2.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Gradle

dependencies {
    testImplementation 'io.rest-assured:rest-assured:5.4.0'
    testImplementation 'io.rest-assured:json-path:5.4.0'
    testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
    testImplementation 'org.hamcrest:hamcrest:2.2'
}

test {
    useJUnitPlatform()
}

Your First Test

Create a test class in src/test/java:

import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

public class ApiTest {

    @BeforeAll
    static void setup() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
    }

    @Test
    void shouldGetUser() {
        given()
        .when()
            .get("/users/1")
        .then()
            .statusCode(200)
            .body("id", equalTo(1))
            .body("name", equalTo("Leanne Graham"))
            .body("email", containsString("@"));
    }

    @Test
    void shouldGetAllPosts() {
        given()
        .when()
            .get("/posts")
        .then()
            .statusCode(200)
            .body("size()", equalTo(100))
            .body("[0].userId", notNullValue())
            .body("[0].title", not(emptyString()));
    }

    @Test
    void shouldCreatePost() {
        String requestBody = """
            {
                "title": "REST Assured Tutorial",
                "body": "Testing APIs with Java",
                "userId": 1
            }
            """;

        given()
            .contentType("application/json")
            .body(requestBody)
        .when()
            .post("/posts")
        .then()
            .statusCode(201)
            .body("title", equalTo("REST Assured Tutorial"))
            .body("id", notNullValue());
    }
}

Run with Maven:

mvn test -Dtest=ApiTest

Understanding the BDD Syntax

REST Assured's fluent API follows BDD structure:

given()        // Setup: headers, params, body, auth
    .header("Authorization", "Bearer token")
    .queryParam("page", 1)
    .body(requestBody)

.when()        // Action: HTTP method + path
    .get("/endpoint")      // GET
    .post("/endpoint")     // POST
    .put("/endpoint")      // PUT
    .patch("/endpoint")    // PATCH
    .delete("/endpoint")   // DELETE

.then()        // Assertions
    .statusCode(200)
    .body("field", equalTo("value"))
    .header("Content-Type", containsString("application/json"));

The given() section is optional when you have no setup:

// Minimal test
when()
    .get("/health")
.then()
    .statusCode(200);

Request Configuration

Headers

given()
    .header("Authorization", "Bearer " + token)
    .header("Accept", "application/json")
    .header("X-Request-ID", UUID.randomUUID().toString())
.when()
    .get("/protected-resource")
.then()
    .statusCode(200);

Query Parameters

given()
    .queryParam("page", 1)
    .queryParam("limit", 20)
    .queryParam("sort", "name")
    .queryParam("order", "asc")
.when()
    .get("/users")
.then()
    .statusCode(200);

Path Parameters

int userId = 42;

given()
    .pathParam("id", userId)
.when()
    .get("/users/{id}")
.then()
    .statusCode(200)
    .body("id", equalTo(userId));

Request Body

// JSON string
given()
    .contentType("application/json")
    .body("{\"name\": \"Alice\", \"email\": \"alice@example.com\"}")
.when()
    .post("/users");

// Map (auto-serialized to JSON)
Map<String, Object> user = new HashMap<>();
user.put("name", "Alice");
user.put("email", "alice@example.com");

given()
    .contentType(ContentType.JSON)
    .body(user)
.when()
    .post("/users");

// Object (with Jackson)
User user = new User("Alice", "alice@example.com");

given()
    .contentType(ContentType.JSON)
    .body(user)
.when()
    .post("/users");

Response Assertions

Status Code and Headers

.then()
    .statusCode(200)
    .statusLine("HTTP/1.1 200 OK")
    .header("Content-Type", "application/json; charset=utf-8")
    .header("X-Rate-Limit", notNullValue())
    .headers("Content-Type", containsString("json"),
             "Cache-Control", notNullValue());

Body Assertions with JsonPath

.then()
    .body("name", equalTo("Alice"))                          // String equality
    .body("age", greaterThan(18))                            // Number comparison
    .body("active", is(true))                                // Boolean
    .body("tags", hasItems("java", "testing"))               // Array contains
    .body("tags.size()", equalTo(3))                         // Array length
    .body("address.city", equalTo("New York"))               // Nested object
    .body("orders[0].total", greaterThan(0.0f))             // Array index
    .body("orders.id", hasItems(101, 102, 103));             // All array field values

Extracting Response Values

// Extract a single value
String name = given()
    .when()
        .get("/users/1")
    .then()
        .statusCode(200)
        .extract()
        .path("name");

System.out.println("User name: " + name);

// Extract the full response body
String responseBody = given()
    .when()
        .get("/users/1")
    .then()
        .extract()
        .body()
        .asString();

// Extract as a typed object (with Jackson)
User user = given()
    .when()
        .get("/users/1")
    .then()
        .extract()
        .as(User.class);

// Extract header
String location = given()
    .contentType(ContentType.JSON)
    .body(newUserJson)
    .when()
        .post("/users")
    .then()
        .statusCode(201)
        .extract()
        .header("Location");

Global Configuration

Avoid repeating base URI, headers, and auth in every test:

import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.junit.jupiter.api.BeforeAll;

public class BaseApiTest {

    protected static RequestSpecification requestSpec;

    @BeforeAll
    static void setupRestAssured() {
        // Global base URI
        RestAssured.baseURI = System.getProperty("api.base.url", "https://api.example.com");

        // Enable request/response logging for failed tests
        RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();

        // Build a reusable request specification
        requestSpec = new RequestSpecBuilder()
            .setContentType(ContentType.JSON)
            .setAccept(ContentType.JSON)
            .addHeader("Authorization", "Bearer " + getAuthToken())
            .addHeader("X-API-Version", "v2")
            .build();
    }

    private static String getAuthToken() {
        // Get token from env var or test config
        return System.getenv().getOrDefault("API_TOKEN", "test-token-123");
    }
}

Use in test classes:

public class UsersApiTest extends BaseApiTest {

    @Test
    void shouldGetCurrentUser() {
        given()
            .spec(requestSpec)
        .when()
            .get("/me")
        .then()
            .statusCode(200)
            .body("email", not(emptyString()));
    }
}

Common Matchers Reference

REST Assured uses Hamcrest matchers. Common ones:

import static org.hamcrest.Matchers.*;

// Equality
equalTo("value")
not(equalTo("other"))
is("value")

// Strings
containsString("substring")
startsWith("prefix")
endsWith("suffix")
matchesPattern("\\d{3}-\\d{4}")
emptyString()
not(emptyOrNullString())

// Numbers
greaterThan(5)
greaterThanOrEqualTo(5)
lessThan(100)
closeTo(9.99, 0.01)  // Within delta

// Collections
hasItem("value")
hasItems("a", "b", "c")
contains("a", "b", "c")  // Exact order
containsInAnyOrder("c", "a", "b")
hasSize(5)
empty()
not(empty())

// Nulls
nullValue()
notNullValue()

// Type
instanceOf(Integer.class)

Running Tests

Maven

# Run all tests
mvn <span class="hljs-built_in">test

<span class="hljs-comment"># Run specific class
mvn <span class="hljs-built_in">test -Dtest=UsersApiTest

<span class="hljs-comment"># Run specific method
mvn <span class="hljs-built_in">test -Dtest=UsersApiTest#shouldGetCurrentUser

<span class="hljs-comment"># With system properties
mvn <span class="hljs-built_in">test -Dapi.base.url=https://staging.api.example.com

JUnit 5 in IDE

Tests run directly from your IDE with the play button. JUnit 5 provides clear pass/fail output per test method.

Logging for Debugging

REST Assured can log requests and responses:

// Log everything
given()
    .log().all()
.when()
    .get("/users/1")
.then()
    .log().all()
    .statusCode(200);

// Log only on failure
given()
    .log().ifValidationFails()
.when()
    .get("/users/1")
.then()
    .log().ifValidationFails()
    .statusCode(200);

// Log specific parts
given()
    .log().headers()
    .log().body()
.when()
    .post("/users")
.then()
    .log().status()
    .log().body();

Enable globally for all failing tests (recommended for CI):

@BeforeAll
static void setup() {
    RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
}

Next Steps

With REST Assured set up, you're ready to explore:

  • Spring Boot integration — testing your own Spring REST endpoints with RestAssuredMockMvc
  • Authentication — OAuth2, JWT, and Basic Auth patterns
  • TestNG integration — using REST Assured with TestNG for parameterized tests
  • BDD style — organizing tests with Given-When-Then using REST Assured's DSL more formally
  • Schema validation — validating response structure against JSON Schema

REST Assured is the foundation for Java API testing. Its fluent API scales from simple smoke tests to comprehensive API test suites that cover authentication, pagination, error handling, and complex business workflows.

Read more