Skip to main content
Back to Blog
Tutorial
2026-02-23

REST Assured Tutorial — Java API Testing from Basics to CI/CD

Complete REST Assured tutorial for Java API testing. Covers given-when-then syntax, request specs, response validation, authentication, and CI/CD integration.

REST Assured is the most widely adopted API testing framework for Java teams, and for good reason. Its fluent given-when-then syntax reads like a specification, Hamcrest matchers make assertions expressive, and deep integration with TestNG and JUnit 5 means it slots into any existing Java project without friction. Whether you are a Java developer writing your first API test or an SDET building a comprehensive regression suite, this REST Assured tutorial walks you through everything -- from your first GET request to a fully automated CI/CD pipeline with Allure reporting.


Key Takeaways

  • REST Assured provides a domain-specific language (DSL) for Java API testing that mirrors BDD-style given-when-then specifications
  • RequestSpecBuilder lets you define reusable base URIs, headers, and authentication so every test starts from a consistent baseline
  • JSON path expressions, Hamcrest matchers, and JSON Schema validation give you three layers of response validation
  • Serialization and deserialization with Jackson or Gson eliminate manual JSON construction and make tests type-safe
  • Filters and logging provide full request/response visibility without cluttering your test logic
  • Integrating REST Assured into GitHub Actions with Maven Surefire and Allure reports gives you automated regression feedback on every pull request

What Is REST Assured?

REST Assured is an open-source Java library that simplifies testing and validating REST APIs. Created by Johan Haleby, it wraps Apache HttpClient behind a fluent DSL that is purpose-built for API testing. Instead of constructing HTTP requests manually and parsing responses with boilerplate, you write expressive chains that read almost like plain English.

The core abstraction follows the given-when-then pattern:

  • given() -- set up the request (headers, query params, body, auth)
  • when() -- execute the HTTP method (GET, POST, PUT, DELETE)
  • then() -- assert the response (status code, body fields, headers)

Why do Java teams choose REST Assured over raw HttpClient or other libraries?

  • Fluent API: Chained method calls produce readable, self-documenting tests
  • Hamcrest integration: Use equalTo(), hasSize(), containsString(), and dozens of other matchers out of the box
  • JSON and XML support: First-class JSON path and XML path expressions for navigating response bodies
  • Schema validation: Validate entire response structures against JSON Schema files
  • Broad ecosystem: Works seamlessly with TestNG, JUnit 5, Maven, Gradle, Allure, and every major CI/CD platform

REST Assured has been downloaded over 50 million times on Maven Central and remains the default choice for Java API testing in enterprise environments.


Getting Started

Maven Dependencies

Add the following to your pom.xml:

<dependencies>
  <dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <version>5.5.0</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>json-schema-validator</artifactId>
    <version>5.5.0</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>7.10.2</version>
    <scope>test</scope>
  </dependency>
</dependencies>

Gradle Dependencies

If you prefer Gradle, add these to build.gradle:

testImplementation 'io.rest-assured:rest-assured:5.5.0'
testImplementation 'io.rest-assured:json-schema-validator:5.5.0'
testImplementation 'org.testng:testng:7.10.2'

Static Imports

REST Assured relies heavily on static imports. Add these to the top of every test class:

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

Your First Test

Here is a complete GET test that validates status code, content type, and response body fields:

import org.testng.annotations.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

public class UserApiTest {

    @Test
    public void shouldReturnUserById() {
        given()
            .baseUri("https://jsonplaceholder.typicode.com")
            .pathParam("id", 1)
        .when()
            .get("/users/{id}")
        .then()
            .statusCode(200)
            .contentType("application/json")
            .body("name", equalTo("Leanne Graham"))
            .body("email", equalTo("Sincere@april.biz"))
            .body("address.city", equalTo("Gwenborough"));
    }
}

This single test validates the HTTP status, content type header, top-level fields, and a nested field inside the address object. The given-when-then structure makes the intent immediately clear to anyone reading the code.


Request Specification

As your test suite grows, you will find yourself repeating the same base URI, headers, and authentication in every test. RequestSpecBuilder solves this by creating reusable specifications.

Building a Reusable Request Spec

import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;

public class ApiTestBase {

    protected static RequestSpecification baseSpec;

    @BeforeClass
    public static void setup() {
        baseSpec = new RequestSpecBuilder()
            .setBaseUri("https://api.example.com")
            .setBasePath("/v2")
            .addHeader("Accept", "application/json")
            .addHeader("X-Api-Version", "2.0")
            .setContentType("application/json")
            .build();
    }
}

Now every test starts with given().spec(baseSpec) and only adds the request-specific details:

@Test
public void shouldListProducts() {
    given()
        .spec(baseSpec)
        .queryParam("category", "electronics")
        .queryParam("limit", 10)
    .when()
        .get("/products")
    .then()
        .statusCode(200)
        .body("products", hasSize(lessThanOrEqualTo(10)));
}

Path Parameters

REST Assured supports named path parameters that make URL construction clean:

given()
    .spec(baseSpec)
    .pathParam("userId", 42)
    .pathParam("orderId", 100)
.when()
    .get("/users/{userId}/orders/{orderId}")
.then()
    .statusCode(200);

Authentication

REST Assured supports multiple authentication schemes:

Basic Auth:

given()
    .auth().basic("username", "password")
.when()
    .get("/protected/resource")
.then()
    .statusCode(200);

OAuth 2.0 Bearer Token:

given()
    .auth().oauth2("your-access-token-here")
.when()
    .get("/api/profile")
.then()
    .statusCode(200);

API Key in Header:

given()
    .header("X-API-Key", "your-api-key")
.when()
    .get("/api/data")
.then()
    .statusCode(200);

For enterprise projects, embed the auth into your RequestSpecBuilder so that every test automatically includes the correct credentials without duplication.


Response Validation

The then() section is where REST Assured truly shines. You can combine Hamcrest matchers, JSON path expressions, and schema validation for comprehensive response assertions.

Hamcrest Matchers

Hamcrest provides a rich library of matchers:

.then()
    .statusCode(200)
    .body("name", equalTo("Widget"))
    .body("price", greaterThan(0.0f))
    .body("tags", hasItems("sale", "new"))
    .body("tags", hasSize(3))
    .body("description", containsString("premium"))
    .body("metadata", notNullValue());

Nested JSON Validation

Use dot notation to navigate deeply nested structures:

.then()
    .body("user.address.geo.lat", equalTo("-37.3159"))
    .body("user.company.name", equalTo("Romaguera-Crona"))
    .body("user.address.zipcode", matchesPattern("\\d{5}(-\\d{4})?"));

Array Validation

REST Assured provides powerful array assertions using JSON path:

.then()
    .body("users", hasSize(10))
    .body("users[0].name", equalTo("Alice"))
    .body("users.findAll { it.age > 30 }.name", hasItems("Bob", "Carol"))
    .body("users.collect { it.email }", everyItem(containsString("@")));

The Groovy GPath syntax lets you filter, collect, and aggregate array elements directly in your assertions.

Header and Cookie Assertions

.then()
    .header("Content-Type", containsString("application/json"))
    .header("Cache-Control", equalTo("no-cache"))
    .cookie("session_id", notNullValue());

JSON Schema Validation

For full structural validation, assert the entire response against a JSON Schema file stored in src/test/resources:

import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;

.then()
    .body(matchesJsonSchemaInClasspath("schemas/user-response.json"));

This catches breaking changes -- missing fields, type changes, unexpected nulls -- automatically. Pair it with your CI pipeline for contract enforcement on every build.


Serialization and Deserialization

Manually constructing JSON strings for request bodies is error-prone and hard to maintain. REST Assured integrates with Jackson and Gson to serialize Java objects to JSON and deserialize responses back into POJOs.

Sending a POJO as Request Body

public class CreateUserRequest {
    private String name;
    private String email;
    private String role;

    // constructors, getters, setters
    public CreateUserRequest(String name, String email, String role) {
        this.name = name;
        this.email = email;
        this.role = role;
    }
    // getters and setters omitted for brevity
}
@Test
public void shouldCreateUser() {
    CreateUserRequest newUser = new CreateUserRequest(
        "Jane Smith", "jane@example.com", "admin"
    );

    given()
        .spec(baseSpec)
        .body(newUser)
    .when()
        .post("/users")
    .then()
        .statusCode(201)
        .body("name", equalTo("Jane Smith"))
        .body("id", notNullValue());
}

REST Assured automatically serializes the CreateUserRequest object to JSON using Jackson (if it is on the classpath) or Gson as a fallback.

Deserializing Responses to Java Objects

@Test
public void shouldDeserializeUserResponse() {
    UserResponse user = given()
        .spec(baseSpec)
        .pathParam("id", 1)
    .when()
        .get("/users/{id}")
    .then()
        .statusCode(200)
        .extract()
        .as(UserResponse.class);

    assertThat(user.getName()).isEqualTo("Leanne Graham");
    assertThat(user.getEmail()).contains("@");
    assertThat(user.getAddress().getCity()).isEqualTo("Gwenborough");
}

Extracting Partial Responses

Sometimes you only need a single field from the response:

String userId = given()
    .spec(baseSpec)
    .body(newUser)
.when()
    .post("/users")
.then()
    .statusCode(201)
    .extract()
    .path("id");

// Use userId in subsequent requests
given()
    .spec(baseSpec)
    .pathParam("id", userId)
.when()
    .get("/users/{id}")
.then()
    .statusCode(200);

This pattern is essential for chained API tests where the output of one request feeds into the next.


Advanced Features

Filters

Filters intercept every request and response, making them ideal for logging, header injection, or token refresh logic:

RestAssured.filters(new RequestLoggingFilter(), new ResponseLoggingFilter());

You can also write custom filters:

RestAssured.filters((requestSpec, responseSpec, filterContext) -> {
    requestSpec.header("X-Request-Id", UUID.randomUUID().toString());
    return filterContext.next(requestSpec, responseSpec);
});

Request and Response Logging

For debugging, enable logging at the request or response level:

given()
    .log().all()    // logs method, URI, headers, body
.when()
    .get("/users")
.then()
    .log().ifError() // only logs response when status >= 400
    .statusCode(200);

Other logging options include .log().body(), .log().headers(), and .log().ifValidationFails().

Multi-Part File Uploads

@Test
public void shouldUploadFile() {
    File testFile = new File("src/test/resources/test-document.pdf");

    given()
        .spec(baseSpec)
        .multiPart("file", testFile, "application/pdf")
        .multiPart("description", "Test document upload")
    .when()
        .post("/documents/upload")
    .then()
        .statusCode(201)
        .body("filename", equalTo("test-document.pdf"))
        .body("size", greaterThan(0));
}

File Downloads

@Test
public void shouldDownloadReport() {
    byte[] fileBytes = given()
        .spec(baseSpec)
    .when()
        .get("/reports/monthly.pdf")
    .then()
        .statusCode(200)
        .contentType("application/pdf")
        .extract()
        .asByteArray();

    assertThat(fileBytes.length).isGreaterThan(0);
}

SSL Configuration

For testing services with self-signed certificates:

RestAssured.useRelaxedHTTPSValidation();

// Or for a specific keystore
RestAssured.trustStore("src/test/resources/truststore.jks", "password");

Redirect Handling

given()
    .redirects().follow(false) // disable auto-follow
.when()
    .get("/old-endpoint")
.then()
    .statusCode(301)
    .header("Location", equalTo("/new-endpoint"));

Integration with TestNG and JUnit 5

REST Assured is framework-agnostic, but it works especially well with TestNG and JUnit 5 -- the two most popular Java test frameworks.

TestNG: Data-Driven Testing with DataProviders

import org.testng.annotations.*;

public class UserApiDataDrivenTest extends ApiTestBase {

    @DataProvider(name = "userIds")
    public Object[][] userIds() {
        return new Object[][] {
            { 1, "Leanne Graham" },
            { 2, "Ervin Howell" },
            { 3, "Clementine Bauch" }
        };
    }

    @Test(dataProvider = "userIds")
    public void shouldReturnCorrectUserName(int id, String expectedName) {
        given()
            .spec(baseSpec)
            .pathParam("id", id)
        .when()
            .get("/users/{id}")
        .then()
            .statusCode(200)
            .body("name", equalTo(expectedName));
    }
}

JUnit 5: @BeforeAll Setup and Parameterized Tests

import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class ProductApiTest {

    @BeforeAll
    static void setup() {
        RestAssured.baseURI = "https://api.example.com";
        RestAssured.basePath = "/v2";
    }

    @ParameterizedTest
    @ValueSource(strings = {"electronics", "books", "clothing"})
    void shouldReturnProductsByCategory(String category) {
        given()
            .queryParam("category", category)
        .when()
            .get("/products")
        .then()
            .statusCode(200)
            .body("products", not(empty()))
            .body("products.every { it.category == '" + category + "' }", is(true));
    }

    @Test
    void shouldReturn404ForNonexistentProduct() {
        given()
            .pathParam("id", 999999)
        .when()
            .get("/products/{id}")
        .then()
            .statusCode(404)
            .body("error", equalTo("Product not found"));
    }
}

Parallel Execution

For TestNG, configure parallel execution in testng.xml:

<suite name="API Tests" parallel="methods" thread-count="8">
  <test name="All API Tests">
    <classes>
      <class name="com.example.tests.UserApiTest"/>
      <class name="com.example.tests.ProductApiTest"/>
    </classes>
  </test>
</suite>

For JUnit 5, add a junit-platform.properties file:

junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.fixed.parallelism=8

Parallel execution can cut your API test suite time by 60-80%, which is critical for fast CI/CD feedback loops.


CI/CD Integration

A REST Assured test suite is only valuable if it runs automatically on every code change. Here is how to integrate it into your CI/CD pipeline.

Maven Surefire Plugin

Configure the Surefire plugin in your pom.xml to discover and run your tests:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>3.2.5</version>
      <configuration>
        <suiteXmlFiles>
          <suiteXmlFile>testng.xml</suiteXmlFile>
        </suiteXmlFiles>
        <parallel>methods</parallel>
        <threadCount>8</threadCount>
      </configuration>
    </plugin>
  </plugins>
</build>

GitHub Actions Workflow

name: API Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  api-tests:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK 21
        uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
          cache: 'maven'

      - name: Run API tests
        run: mvn test -Dtest.env=ci
        env:
          API_BASE_URL: ${{ secrets.API_BASE_URL }}
          API_TOKEN: ${{ secrets.API_TOKEN }}

      - name: Generate Allure report
        if: always()
        uses: simple-elf/allure-report-action@v1.7
        with:
          allure_results: target/allure-results

      - name: Upload Allure report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: allure-report
          path: allure-report

Allure Report Generation

Add the Allure dependency and annotations for rich reporting:

<dependency>
  <groupId>io.qameta.allure</groupId>
  <artifactId>allure-rest-assured</artifactId>
  <version>2.27.0</version>
  <scope>test</scope>
</dependency>

Attach the Allure filter to capture request/response details in reports:

@BeforeAll
static void setup() {
    RestAssured.filters(new AllureRestAssured());
}

Now every test execution produces detailed HTML reports showing request parameters, response bodies, and timing. For a deeper dive into CI/CD pipeline design, see our CI/CD testing pipeline guide.


REST Assured vs Alternatives

Choosing the right API testing framework for Java depends on your team's language preference, existing toolchain, and testing complexity. Here is how REST Assured compares to the leading alternatives:

FeatureREST AssuredPlaywright APIKarate DSLJava HttpClient
LanguageJavaTypeScript/JSGherkin + JavaJava
Syntax styleFluent given-when-thenAsync/awaitBDD feature filesImperative
Learning curveLow for Java devsLow for JS devsMediumMedium
JSON path supportBuilt-in (GPath)Manual parsingBuilt-inManual parsing
Schema validationJSON Schema moduleThird-partyBuilt-inManual
Parallel executionVia TestNG/JUnitBuilt-inBuilt-inManual
ReportingAllure, ExtentReportsPlaywright HTMLCucumber reportsManual
Community sizeVery largeLargeMediumLarge (JDK standard)
Best forJava API testingFull-stack JS teamsBDD-focused teamsMinimal dependency

When to choose REST Assured: You are a Java team, you want expressive fluent syntax, and you need tight integration with TestNG or JUnit. It is the default choice for Java API testing in enterprise environments.

When to choose Playwright API: Your team already uses Playwright for E2E testing and wants a single toolchain for both UI and API tests.

When to choose Karate: You want non-programmers (BAs, manual testers) to write API tests using Gherkin syntax.

When to choose Java HttpClient: You need zero external dependencies and are comfortable writing more verbose code.


Automate API Testing with AI Agents

AI coding agents like Claude Code, Cursor, and Windsurf can generate REST Assured tests from your API specifications -- but only if they have the right context. Installing a QA skill gives your agent expert-level knowledge of REST Assured patterns, best practices, and common pitfalls.

Install REST Assured testing skills with a single command:

npx @qaskills/cli add rest-assured-api
npx @qaskills/cli add restassured-api-framework

Once installed, your AI agent will:

  • Generate tests that follow the given-when-then pattern consistently
  • Use RequestSpecBuilder for reusable configurations instead of duplicating setup
  • Apply proper Hamcrest matchers for expressive, readable assertions
  • Include JSON Schema validation for contract enforcement
  • Structure test suites with proper base classes, data providers, and parallel execution

Browse all available QA skills at /skills or get started with the getting started guide. For broader API testing strategies beyond REST Assured, see our API testing complete guide.


Frequently Asked Questions

Is REST Assured still relevant in 2026?

Yes. REST Assured remains the most popular Java API testing library by a wide margin. It receives regular updates, has a massive community, and integrates with every major Java testing framework and CI/CD platform. For Java teams, it is still the best choice for API test automation.

Can REST Assured test GraphQL APIs?

REST Assured can send POST requests with GraphQL query bodies, but it does not have built-in GraphQL awareness. You would send the query as a JSON body with a query field and validate the response like any other JSON. For teams heavily invested in GraphQL, consider pairing REST Assured with a GraphQL-specific validation library.

How does REST Assured handle asynchronous APIs?

REST Assured is inherently synchronous -- it sends a request and waits for the response. For async APIs (webhooks, message queues, long-running operations), you need to combine REST Assured with polling strategies. Use Awaitility to poll an endpoint until the expected state is reached:

await().atMost(30, SECONDS).pollInterval(2, SECONDS).until(() -> {
    int status = given().spec(baseSpec)
        .pathParam("id", jobId)
        .get("/jobs/{id}")
        .then().extract().path("status");
    return "completed".equals(status);
});

What is the difference between REST Assured and Karate?

REST Assured is a Java library that you use inside standard Java test classes. Karate is a standalone framework that uses Gherkin-like feature files to define API tests. REST Assured gives you full control of Java and integrates naturally with TestNG/JUnit, while Karate is designed for teams that prefer a BDD-style, no-code approach to API testing.

How do I run REST Assured tests in parallel safely?

The key is avoiding shared mutable state. Do not use static fields for test data, create unique resources per test (use unique IDs or timestamps), and configure your test runner for parallel execution. With TestNG, set parallel="methods" in your suite XML. With JUnit 5, enable parallel mode via junit-platform.properties. REST Assured itself is thread-safe as long as you avoid modifying RestAssured.baseURI or other static config during test execution -- use RequestSpecBuilder instead.

REST Assured Tutorial — Java API Testing from Basics to CI/CD | QASkills.sh