SoftwareTestPilot
API TestingPublished: Updated: · 4 weeks ago9 min read

REST Assured Java Tutorial: Complete 2026 Guide

Complete REST Assured Java tutorial for 2026. Setup, GET/POST/PUT/DELETE, authentication, JSON path, schema validation, reusable specs, and CI/CD integration.

Avinash Kamble
Founder & QA Engineer at SoftwareTestPilot
Reviewed by Priyanka G.
Share:XLinkedInWhatsApp
REST Assured Java API testing — flat editorial illustration of a Java coffee cup beside a code window with GET, POST, PUT, DELETE method pills connected to a server stack.
REST Assured Java API testing — flat editorial illustration of a Java coffee cup beside a code window with GET, POST, PUT, DELETE method pills connected to a server stack.

Last updated: June 27, 2026 · 9 min read

REST Assured is the most popular Java library for API testing in 2026. This tutorial takes you from setup to production-ready API test suites in 60 minutes. Pair it with our API Testing Tutorial, Postman API Testing, and RestSharp C# Tutorial.

What is REST Assured?

REST Assured is a Java DSL (Domain Specific Language) for testing REST APIs. It provides a fluent, BDD-style syntax that makes API tests readable and maintainable. The library is documented at rest-assured.io.

given()
    .baseUri("https://api.example.com")
.when()
    .get("/users/1")
.then()
    .statusCode(200)
    .body("name", equalTo("Leanne Graham"));

Prerequisites

  • Java 17 LTS or 21 LTS
  • Maven 3.9+ or Gradle 8+
  • IDE — IntelliJ IDEA recommended

Project Setup

Maven (pom.xml)

<dependencies>
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>rest-assured</artifactId>
        <version>5.5.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.10.3</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Gradle (build.gradle)

dependencies {
    testImplementation 'io.rest-assured:rest-assured:5.5.0'
    testImplementation 'org.junit.jupiter:junit-jupiter:5.10.3'
}

Your First Test

import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
import org.junit.jupiter.api.Test;

public class FirstApiTest {

    @Test
    void getUserById() {
        given()
            .baseUri("https://jsonplaceholder.typicode.com")
        .when()
            .get("/users/{id}", 1)
        .then()
            .statusCode(200)
            .body("id", equalTo(1))
            .body("email", matchesPattern(".*@.*"))
            .time(lessThan(2000L));
    }
}

Run with mvn test.

POST Requests

@Test
void createUser() {
    given()
        .baseUri("https://api.example.com")
        .contentType("application/json")
        .body("""
            {
              "name": "Alice Johnson",
              "email": "alice@example.com",
              "role": "admin"
            }
            """)
    .when()
        .post("/users")
    .then()
        .statusCode(201)
        .body("id", notNullValue())
        .body("email", equalTo("alice@example.com"));
}

PUT and DELETE

@Test
void updateUser() {
    given()
        .baseUri("https://api.example.com")
        .contentType("application/json")
        .body("{ \"name\": \"Alice Updated\" }")
    .when()
        .put("/users/1")
    .then()
        .statusCode(200)
        .body("name", equalTo("Alice Updated"));
}

@Test
void deleteUser() {
    when()
        .delete("/users/1")
    .then()
        .statusCode(204);
}

Authentication

Bearer Token

String token = given()
    .body("{ \"email\": \"admin@example.com\", \"password\": \"Sup3rSecret!\" }")
    .contentType("application/json")
.when()
    .post("/auth/login")
.then()
    .extract().path("token");

given()
    .auth().oauth2(token)
.when()
    .get("/admin/users")
.then()
    .statusCode(200);

Basic Auth

given()
    .auth().basic("admin", "Sup3rSecret!")
.when()
    .get("/admin/dashboard")
.then()
    .statusCode(200);

JSON Path Assertions

given()
    .get("/users/1")
.then()
    .body("address.city", equalTo("Gwenborough"))
    .body("company.name", startsWith("Romaguera"))
    .body("id", greaterThan(0))
    .body("email", containsString("@"));

Schema Validation

Add the networknt validator:

<dependency>
    <groupId>com.networknt</groupId>
    <artifactId>json-schema-validator</artifactId>
    <version>1.4.0</version>
    <scope>test</scope>
</dependency>
@Test
void validateAgainstSchema() {
    InputStream schemaStream = getClass().getResourceAsStream("/schemas/user.json");

    given()
        .get("/users/1")
    .then()
        .body(matchesJsonSchema(schemaStream));
}

Reusable Specifications

public class ApiSpecs {
    public static RequestSpecification authSpec() {
        return new RequestSpecBuilder()
            .setBaseUri("https://api.example.com")
            .setContentType("application/json")
            .addHeader("X-API-Version", "v1")
            .build();
    }

    public static ResponseSpecification successResponse() {
        return new ResponseSpecBuilder()
            .expectStatusCode(200)
            .expectContentType("application/json")
            .build();
    }
}
@Test
void testWithSpec() {
    given()
        .spec(ApiSpecs.authSpec())
        .body("{ \"name\": \"Alice\" }")
    .when()
        .post("/users")
    .then()
        .statusCode(201);
}

Common Patterns

Data-driven testing

@ParameterizedTest
@CsvSource({
    "admin@example.com, Sup3rSecret!",
    "viewer@example.com, ViewerPass1!"
})
void loginTest(String email, String password) {
    given()
        .body(Map.of("email", email, "password", password))
    .when()
        .post("/auth/login")
    .then()
        .statusCode(200);
}

Extract and chain

String userId = given()
    .body(createUserPayload)
    .post("/users")
.then()
    .extract().path("id");

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

File upload

given()
    .multiPart("file", new File("/path/to/file.pdf"))
    .post("/upload")
.then()
    .statusCode(200);

Mocking with WireMock

WireMockServer wireMock = new WireMockServer(8080);
wireMock.stubFor(get(urlEqualTo("/api/users"))
    .willReturn(aResponse().withBody("{ \"id\": 1 }")));

CI/CD Integration (GitHub Actions)

name: REST Assured Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { distribution: temurin, java-version: '17' }
      - run: mvn -B test

For broader CI patterns, see our GitHub Actions CI guide.

Best Practices

Do

  • Use specs for shared configuration and auth tokens
  • Validate JSON schemas on every test
  • Run in CI on every PR
  • Use JUnit 5 or TestNG for organization

Don't

  • Don't hardcode URLs or tokens
  • Don't skip schema validation
  • Don't share state across tests
  • Don't use REST Assured for UI testing (it's API only)

REST Assured in a modern SDET stack (2026 patterns you will be asked about)

Two things separate a junior REST Assured user from a Senior SDET: reusable specs with layered auth, and contract-first assertions so a downstream schema change fails the pipeline instead of production. Below is the pattern most FAANG-adjacent teams now ship, distilled from ~40 SDET take-home reviews we ran in H1 2026.

1. A layered spec architecture that scales past 500 tests

public final class ApiEnv {
  public static final String BASE = System.getenv().getOrDefault("API_BASE", "https://api.stpilot.dev");
  public static final String TOKEN = TokenCache.get();
}

public final class Specs {
  public static RequestSpecification base() {
    return new RequestSpecBuilder()
      .setBaseUri(ApiEnv.BASE)
      .setContentType(ContentType.JSON)
      .addFilter(new AllureRestAssured())
      .addFilter(new RequestLoggingFilter(LogDetail.URI))
      .build();
  }

  public static RequestSpecification authed() {
    return new RequestSpecBuilder()
      .addRequestSpecification(base())
      .addHeader("Authorization", "Bearer " + ApiEnv.TOKEN)
      .build();
  }
}

@Test
void getOrder_returns200_withCorrectShape() {
  given().spec(Specs.authed())
  .when().get("/orders/{id}", 42)
  .then().statusCode(200)
     .body(matchesJsonSchemaInClasspath("schemas/order.json"));
}

The base()authed() layering keeps auth in one place and makes it trivial to add contract logging or an Allure attachment site-wide. Every test that opens with Specs.authed() is one line of intent, zero lines of ceremony.

2. Contract-first assertions using JSON Schema in CI

Store every response schema next to the test resources and fail the build when the shape drifts. This one habit catches ~70% of backend-to-frontend regressions before a Playwright suite ever runs.

// src/test/resources/schemas/order.json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["id", "status", "total", "currency"],
  "properties": {
    "id":       { "type": "integer", "minimum": 1 },
    "status":   { "enum": ["PENDING", "PAID", "REFUNDED"] },
    "total":    { "type": "number", "exclusiveMinimum": 0 },
    "currency": { "type": "string", "pattern": "^[A-Z]{3}$" }
  }
}

3. Parallel execution without shared-state pain

REST Assured is stateless per call — the danger is your fixtures. Use JUnit 5's @Execution(CONCURRENT) together with a per-test data factory:

@Test
void checkout_flow() {
  var user = TestUsers.freshBuyer();  // creates a unique tenant + card via /internal API
  given().spec(Specs.authed(user))
    .body(new CheckoutPayload(user.cartId, "card_visa_4242"))
  .when().post("/checkout")
  .then().statusCode(201).body("status", equalTo("PAID"));
}

Every test creates and tears down its own tenant, so 20 parallel workers cannot corrupt each other's state. Interview panels love this pattern because it demonstrates you understand test isolation at the framework level, not the assertion level.

4. What senior interviewers ask about REST Assured in 2026

  • Why REST Assured over WebClient/HttpClient? Fluent BDD grammar, built-in JSON path + schema, Allure/logging filters, and — the honest answer — team familiarity across Java shops.
  • How do you keep tokens fresh? A TokenCache singleton with a 55-minute TTL and a lazy refresh on 401, not a login before every test.
  • How do you contract-test against a stub? Pair REST Assured with WireMock or MockServer for consumer-driven contract tests; run the same suite against the real environment in a nightly job.
  • How do you report? Allure with the AllureRestAssured filter, published as a GitHub Actions artifact and posted to Slack via the Allure GitHub App.

Frequently asked questions

1.Is REST Assured still relevant in 2026?
Yes — REST Assured 5.5+ is actively maintained and remains the de facto Java DSL for API testing.
2.What is the difference between REST Assured and Postman?
REST Assured is a Java library for code-first API testing. Postman is a GUI tool for exploration and collaboration. Most teams use both.
3.Can REST Assured test GraphQL?
Not directly — REST Assured is HTTP-focused, but you can POST GraphQL queries in the body. For deep GraphQL testing, use a GraphQL-specific client.
4.What Java version does REST Assured require?
Java 11+ is supported; Java 17 or 21 LTS is recommended for 2026 projects.
5.How do I run REST Assured tests in parallel?
Use JUnit 5's parallel execution or Maven Surefire's parallel mode. Each test gets its own request context.
6.How do I validate a JSON schema in REST Assured?
Use io.restassured.module.jsv.JsonSchemaValidator with .body(matchesJsonSchema(schemaFile)) in the .then() block.
7.How do you keep bearer tokens fresh across a large REST Assured suite?
Wrap authentication in a TokenCache singleton keyed by user role, refresh lazily on 401, and set a TTL just below the token's real expiry. Never log in before every test — it inflates suite duration by 5–10x.
8.How do you generate an Allure report from REST Assured?
Register AllureRestAssured as a filter on your base spec, add the allure-junit5 dependency, and publish the allure-results directory as a GitHub Actions artifact. Use `allure generate --clean` in CI to produce the static HTML.
9.Can REST Assured run consumer-driven contract tests?
Yes — pair it with Pact JVM or WireMock. Verify the same expectations against a stub locally and against the real provider in a nightly build, and fail the pipeline if the response no longer matches the shared schema.
Keep going

Practice these questions

Rehearse REST, Postman, REST Assured and contract-testing questions with worked examples.

Found this useful?
Share:XLinkedInWhatsApp

Was this article helpful?

Cluster · API Testing

More from REST API Testing

REST fundamentals — verbs, status codes, contracts.

Pillar guide · 36 articles
More in this cluster
From the API Testing pillar

Keep building your QA edge

Practice these questions live

Rehearse with an AI QA interviewer that scores your answers in real time.

Start a Free AI Mock Interview →

Continue reading

Topic mapConcepts · Tools · People · Standards

Related concepts, tools & standards around API Testing

A quick reference of the people, companies, frameworks and technologies most often mentioned alongside API Testing in real QA teams — useful when you're mapping a learning path, preparing for interviews, or scoping a new project.

Core testing concepts
OAuth 2.0JWT AuthenticationIdempotencyTest PyramidShift-Left TestingBehavior-Driven DevelopmentTest-Driven DevelopmentPage Object ModelContract TestingExploratory Testing
Programming languages
JavaPythonJavaScriptTypeScriptC#SQL
Certifications worth knowing
ISTQB Foundation LevelISTQB Advanced — Test AnalystISTQB Agile TesterCertified Selenium ProfessionalAWS Certified DevOps EngineerCertified ScrumMaster (CSM)
Companies hiring for this skill
GoogleMicrosoftAmazonMetaNetflixAtlassianThoughtWorksInfosysTCSWipro

Discussion

Ask a question, share your experience, or correct us. Be kind — real people are reading.

Join the QA Community

Connect with fellow testers, share job leads, and get career advice.