
REST Assured Complete Guide: Java API Testing Made Simple
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.
Table Of Contents-
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 methodthen()- 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 APIHeaders 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
| Matcher | Description | Example |
|---|---|---|
equalTo() | Exact match | body("name", equalTo("John")) |
containsString() | Contains substring | body("email", containsString("@")) |
hasItems() | Collection contains | body("tags", hasItems("api", "test")) |
hasSize() | Collection size | body("items", hasSize(5)) |
greaterThan() | Numeric comparison | body("count", greaterThan(0)) |
notNullValue() | Not null | body("id", notNullValue()) |
nullValue() | Is null | body("deleted", nullValue()) |
empty() | Empty collection | body("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 secondsWorking 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?