API Automation
REST Assured Complete Guide

REST Assured Complete Guide: Java API Testing Made Simple

Parul Dhingra - Senior Quality Analyst
Parul Dhingra13+ Years ExperienceHire Me

Senior Quality Analyst

Updated: 1/23/2026

Testing REST APIs is a critical skill for modern software development, and REST Assured makes it remarkably straightforward in Java. This library provides a domain-specific language (DSL) that reads almost like natural English, making API tests easy to write and maintain.

Whether you're testing microservices, validating backend integrations, or building a comprehensive API test suite, REST Assured gives you the tools you need without unnecessary complexity.

What is REST Assured?

REST Assured is a Java library designed specifically for testing REST APIs. It simplifies HTTP request construction and response validation through a fluent, BDD-style syntax.

Key features:

  • Fluent API with Given-When-Then syntax
  • JSON and XML parsing built-in
  • Response validation with matchers
  • Authentication support (Basic, OAuth, etc.)
  • Request/response logging
  • Integration with TestNG and JUnit

Why use REST Assured?

  • Readable tests that serve as documentation
  • Less boilerplate than raw HTTP clients
  • Powerful JSON/XML path assertions
  • Strong Java ecosystem integration

Setup and Configuration

Maven Dependencies

Add to your pom.xml:

<dependencies>
    <!-- REST Assured -->
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>rest-assured</artifactId>
        <version>5.4.0</version>
        <scope>test</scope>
    </dependency>
 
    <!-- JSON Schema validation -->
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>json-schema-validator</artifactId>
        <version>5.4.0</version>
        <scope>test</scope>
    </dependency>
 
    <!-- TestNG or JUnit -->
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>7.9.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Gradle Dependencies

dependencies {
    testImplementation 'io.rest-assured:rest-assured:5.4.0'
    testImplementation 'io.rest-assured:json-schema-validator:5.4.0'
    testImplementation 'org.testng:testng:7.9.0'
}

Static Imports

Add these imports to access REST Assured methods cleanly:

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

Your First API Test

REST Assured uses a Given-When-Then structure:

import org.testng.annotations.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
 
public class FirstApiTest {
 
    @Test
    public void getUserReturnsCorrectData() {
        given()
            .baseUri("https://jsonplaceholder.typicode.com")
        .when()
            .get("/users/1")
        .then()
            .statusCode(200)
            .body("name", equalTo("Leanne Graham"))
            .body("email", containsString("@"));
    }
}

Breaking it down:

  • given() - Set up the request (headers, params, body)
  • when() - Execute the HTTP method
  • then() - Validate the response

The Given-When-Then pattern aligns with BDD principles: Given a request setup, When I make the request, Then I expect certain results.

Making HTTP Requests

GET Requests

// Simple GET
get("/users").then().statusCode(200);
 
// GET with path parameter
given()
    .pathParam("id", 1)
.when()
    .get("/users/{id}")
.then()
    .statusCode(200);
 
// GET with query parameters
given()
    .queryParam("page", 2)
    .queryParam("limit", 10)
.when()
    .get("/users")
.then()
    .statusCode(200);

POST Requests

// POST with JSON body
given()
    .contentType("application/json")
    .body("""
        {
            "name": "John Doe",
            "email": "john@example.com"
        }
        """)
.when()
    .post("/users")
.then()
    .statusCode(201)
    .body("id", notNullValue());
 
// POST with Map
Map<String, Object> user = new HashMap<>();
user.put("name", "John Doe");
user.put("email", "john@example.com");
 
given()
    .contentType(ContentType.JSON)
    .body(user)
.when()
    .post("/users")
.then()
    .statusCode(201);
 
// POST with POJO
User newUser = new User("John Doe", "john@example.com");
 
given()
    .contentType(ContentType.JSON)
    .body(newUser)
.when()
    .post("/users")
.then()
    .statusCode(201);

PUT and PATCH Requests

// PUT - Replace entire resource
given()
    .contentType(ContentType.JSON)
    .body(updatedUser)
.when()
    .put("/users/1")
.then()
    .statusCode(200);
 
// PATCH - Partial update
given()
    .contentType(ContentType.JSON)
    .body("{\"email\": \"newemail@example.com\"}")
.when()
    .patch("/users/1")
.then()
    .statusCode(200);

DELETE Requests

given()
    .pathParam("id", 1)
.when()
    .delete("/users/{id}")
.then()
    .statusCode(204);  // or 200, depending on API

Headers and Cookies

given()
    .header("Authorization", "Bearer token123")
    .header("Accept", "application/json")
    .cookie("session", "abc123")
.when()
    .get("/protected-resource")
.then()
    .statusCode(200);
 
// Multiple headers
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer token");
headers.put("X-Custom-Header", "value");
 
given()
    .headers(headers)
.when()
    .get("/endpoint");

Response Validation

Status Code Validation

.then()
    .statusCode(200)                    // Exact match
    .statusCode(lessThan(300))          // Range
    .statusCode(anyOf(is(200), is(201))) // Multiple options
    .statusLine(containsString("OK"));

Body Validation with JsonPath

// Response: {"id": 1, "name": "John", "email": "john@example.com"}
 
.then()
    .body("name", equalTo("John"))
    .body("id", greaterThan(0))
    .body("email", containsString("@"));
 
// Nested objects: {"user": {"address": {"city": "New York"}}}
.body("user.address.city", equalTo("New York"));
 
// Arrays: {"items": [{"name": "A"}, {"name": "B"}]}
.body("items[0].name", equalTo("A"))
.body("items.name", hasItems("A", "B"))
.body("items.size()", equalTo(2));

Common Hamcrest Matchers

MatcherDescriptionExample
equalTo()Exact matchbody("name", equalTo("John"))
containsString()Contains substringbody("email", containsString("@"))
hasItems()Collection containsbody("tags", hasItems("api", "test"))
hasSize()Collection sizebody("items", hasSize(5))
greaterThan()Numeric comparisonbody("count", greaterThan(0))
notNullValue()Not nullbody("id", notNullValue())
nullValue()Is nullbody("deleted", nullValue())
empty()Empty collectionbody("errors", empty())

Header Validation

.then()
    .header("Content-Type", containsString("application/json"))
    .header("X-Rate-Limit", notNullValue())
    .headers("Cache-Control", equalTo("no-cache"));

Response Time Validation

.then()
    .time(lessThan(2000L));  // Response under 2 seconds

Working with JSON

Extracting Values

// Extract single value
String name = given()
    .get("/users/1")
.then()
    .extract().path("name");
 
// Extract entire response
Response response = given()
    .get("/users/1")
.then()
    .extract().response();
 
String name = response.path("name");
int statusCode = response.statusCode();
String body = response.asString();
 
// Extract as POJO
User user = given()
    .get("/users/1")
.then()
    .extract().as(User.class);

JSON Path Queries

// Response:
// {
//   "store": {
//     "books": [
//       {"title": "Book A", "price": 10},
//       {"title": "Book B", "price": 20}
//     ]
//   }
// }
 
// Get all book titles
.body("store.books.title", hasItems("Book A", "Book B"));
 
// Get first book price
.body("store.books[0].price", equalTo(10));
 
// Find books with price > 15
.body("store.books.findAll { it.price > 15 }.title", hasItem("Book B"));
 
// Get max price
.body("store.books.max { it.price }.price", equalTo(20));

Deserializing Responses

// POJO class
public class User {
    private int id;
    private String name;
    private String email;
    // getters and setters
}
 
// Extract as object
User user = given()
    .get("/users/1")
    .as(User.class);
 
assertEquals("Leanne Graham", user.getName());
 
// Extract list
List<User> users = given()
    .get("/users")
    .as(new TypeRef<List<User>>() {});
 
assertEquals(10, users.size());

Request Specifications

Avoid repetition with request specifications:

public class ApiTestBase {
    protected RequestSpecification requestSpec;
 
    @BeforeClass
    public void setup() {
        requestSpec = new RequestSpecBuilder()
            .setBaseUri("https://api.example.com")
            .setBasePath("/v1")
            .setContentType(ContentType.JSON)
            .addHeader("Authorization", "Bearer " + getToken())
            .addFilter(new RequestLoggingFilter())
            .addFilter(new ResponseLoggingFilter())
            .build();
    }
}
 
// Usage in tests
@Test
public void testGetUser() {
    given()
        .spec(requestSpec)
    .when()
        .get("/users/1")
    .then()
        .statusCode(200);
}

Global Configuration

@BeforeClass
public void globalSetup() {
    RestAssured.baseURI = "https://api.example.com";
    RestAssured.basePath = "/v1";
    RestAssured.authentication = oauth2("token");
 
    // Default content type
    RestAssured.requestSpecification = new RequestSpecBuilder()
        .setContentType(ContentType.JSON)
        .build();
}

Response Specifications

Define reusable response expectations:

ResponseSpecification successSpec = new ResponseSpecBuilder()
    .expectStatusCode(200)
    .expectContentType(ContentType.JSON)
    .expectHeader("X-Request-Id", notNullValue())
    .build();
 
ResponseSpecification errorSpec = new ResponseSpecBuilder()
    .expectStatusCode(anyOf(is(400), is(401), is(404)))
    .expectBody("error", notNullValue())
    .build();
 
// Usage
@Test
public void testSuccess() {
    given()
        .spec(requestSpec)
    .when()
        .get("/users/1")
    .then()
        .spec(successSpec)
        .body("name", notNullValue());
}

Logging and Debugging

Request/Response Logging

// Log everything
given()
    .log().all()  // Log request
.when()
    .get("/users/1")
.then()
    .log().all();  // Log response
 
// Log only on failure
.then()
    .log().ifValidationFails();
 
// Log specific parts
given()
    .log().headers()
    .log().body()
.when()
    .get("/endpoint")
.then()
    .log().status()
    .log().body();

Configuring Logging

// Pretty print JSON
RestAssured.config = RestAssured.config()
    .logConfig(LogConfig.logConfig()
        .enableLoggingOfRequestAndResponseIfValidationFails()
        .enablePrettyPrinting(true));

Debugging Tips

// Print response for debugging
Response response = get("/users/1");
System.out.println("Status: " + response.statusCode());
System.out.println("Body: " + response.asPrettyString());
System.out.println("Headers: " + response.headers());
 
// Use filters for detailed logging
given()
    .filter(new RequestLoggingFilter())
    .filter(new ResponseLoggingFilter())
.when()
    .get("/users");

Best Practices

Use Meaningful Test Names

// Good
@Test
public void getUser_WithValidId_ReturnsUserDetails() { }
 
@Test
public void createUser_WithDuplicateEmail_Returns409Conflict() { }
 
// Avoid
@Test
public void test1() { }

Organize with Page Objects (API Objects)

public class UserApi {
    private static final String BASE_PATH = "/users";
 
    public static Response getUser(int id) {
        return given()
            .spec(TestConfig.requestSpec)
        .when()
            .get(BASE_PATH + "/" + id);
    }
 
    public static Response createUser(User user) {
        return given()
            .spec(TestConfig.requestSpec)
            .body(user)
        .when()
            .post(BASE_PATH);
    }
 
    public static Response deleteUser(int id) {
        return given()
            .spec(TestConfig.requestSpec)
        .when()
            .delete(BASE_PATH + "/" + id);
    }
}
 
// Usage in tests
@Test
public void testCreateAndDeleteUser() {
    User user = new User("Test", "test@example.com");
 
    // Create
    int userId = UserApi.createUser(user)
        .then()
        .statusCode(201)
        .extract().path("id");
 
    // Delete
    UserApi.deleteUser(userId)
        .then()
        .statusCode(204);
}

Externalize Test Data

// config.properties
api.base.url=https://api.example.com
api.version=v1
 
// TestConfig.java
public class TestConfig {
    private static Properties props;
 
    static {
        props = new Properties();
        props.load(new FileInputStream("config.properties"));
    }
 
    public static String getBaseUrl() {
        return props.getProperty("api.base.url");
    }
}

Clean Up Test Data

@Test
public void testUserLifecycle() {
    int userId = 0;
    try {
        // Create
        userId = createUser().extract().path("id");
 
        // Test operations
        getUser(userId).then().statusCode(200);
 
    } finally {
        // Always clean up
        if (userId > 0) {
            deleteUser(userId);
        }
    }
}

REST Assured transforms API testing from tedious HTTP handling into expressive, maintainable test code. By leveraging its fluent API, request/response specifications, and powerful validation capabilities, you can build comprehensive API test suites that catch bugs early and document your API behavior clearly.

Quiz on REST Assured

Your Score: 0/10

Question: What is the primary structure used in REST Assured tests?

Continue Reading

Frequently Asked Questions (FAQs) / People Also Ask (PAA)

Do I need a testing framework to use REST Assured?

How does REST Assured compare to using HttpClient directly?

Can REST Assured handle XML responses?

How do I test APIs that require authentication?

What's the difference between body() and extract().path()?

How do I handle file uploads with REST Assured?

Can I reuse test setup across multiple test classes?

How do I test APIs with dynamic or generated values?