Master API Testing in Java with Hands-On Examples
REST Assured is a powerful Java library that simplifies testing and validating REST APIs. It brings the simplicity of dynamic languages into Java, making API testing intuitive and readable.
Write tests in a BDD (Behavior-Driven Development) style that reads like plain English
Support for all HTTP methods, authentication, headers, cookies, file uploads, and more
Built-in matchers and assertions for comprehensive response validation
Works seamlessly with TestNG, JUnit, Maven, and CI/CD pipelines
Instead of writing verbose HTTP client code, REST Assured lets you write clean, expressive tests that focus on what you're testing, not how to make HTTP requests. It's the industry standard for API testing in Java.
Add REST Assured to your project by including these dependencies in your pom.xml:
<dependencies>
<!-- REST Assured -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>5.3.2</version>
<scope>test</scope>
</dependency>
<!-- JSON Schema Validation -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>5.3.2</version>
<scope>test</scope>
</dependency>
<!-- TestNG or JUnit -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.8.0</version>
<scope>test</scope>
</dependency>
</dependencies>
For all examples in this tutorial, you'll need these static imports:
import static io.restassured.RestAssured.*;
import static io.restassured.matcher.RestAssuredMatchers.*;
import static org.hamcrest.Matchers.*;
import io.restassured.response.Response;
Let's start with a simple GET request. REST Assured uses a fluent API with a Given-When-Then structure:
@Test
public void testGetSingleUser() {
given()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.get("/users/1")
.then()
.statusCode(200)
.body("name", equalTo("Leanne Graham"))
.body("email", equalTo("Sincere@april.biz"));
}
@Test
public void testGetUsersWithParams() {
given()
.baseUri("https://jsonplaceholder.typicode.com")
.queryParam("_page", 1)
.queryParam("_limit", 5)
.when()
.get("/users")
.then()
.statusCode(200)
.body("size()", equalTo(5));
}
@Test
public void testExtractResponse() {
Response response = given()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.get("/users/1")
.then()
.statusCode(200)
.extract()
.response();
// Print response
System.out.println("Response Body: " + response.asString());
// Extract specific values
String name = response.path("name");
String email = response.path("email");
System.out.println("Name: " + name);
System.out.println("Email: " + email);
}
POST requests send data to create new resources. You can send JSON, XML, or form data.
@Test
public void testCreateUser() {
String requestBody = """
{
"name": "Jane Doe",
"email": "jane@example.com",
"age": 28
}
""";
given()
.baseUri("https://jsonplaceholder.typicode.com")
.header("Content-Type", "application/json")
.body(requestBody)
.when()
.post("/users")
.then()
.statusCode(201)
.body("name", equalTo("Jane Doe"))
.body("email", equalTo("jane@example.com"));
}
public class User {
private String name;
private String email;
private int age;
// Constructors
public User() {}
public User(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
@Test
public void testCreateUserWithPOJO() {
User user = new User("John Smith", "john@example.com", 32);
given()
.baseUri("https://jsonplaceholder.typicode.com")
.contentType("application/json")
.body(user)
.when()
.post("/users")
.then()
.statusCode(201)
.body("name", equalTo("John Smith"));
}
REST Assured automatically converts POJOs to JSON using Jackson. No manual conversion needed!
PUT replaces the entire resource, while PATCH updates specific fields.
@Test
public void testUpdateUser() {
String updatedUser = """
{
"id": 1,
"name": "Updated Name",
"email": "updated@example.com",
"age": 30
}
""";
given()
.baseUri("https://jsonplaceholder.typicode.com")
.contentType("application/json")
.body(updatedUser)
.when()
.put("/users/1")
.then()
.statusCode(200)
.body("name", equalTo("Updated Name"));
}
@Test
public void testPatchUser() {
String partialUpdate = """
{
"email": "newemail@example.com"
}
""";
given()
.baseUri("https://jsonplaceholder.typicode.com")
.contentType("application/json")
.body(partialUpdate)
.when()
.patch("/users/1")
.then()
.statusCode(200)
.body("email", equalTo("newemail@example.com"));
}
DELETE requests remove resources from the server.
@Test
public void testDeleteUser() {
given()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.delete("/users/1")
.then()
.statusCode(200); // JSONPlaceholder returns 200, many APIs return 204
}
REST Assured provides powerful validation capabilities using Hamcrest matchers.
@Test
public void testStatusCodes() {
// Exact status code
given()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.get("/users/1")
.then()
.statusCode(200);
// Status code in range
given()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.post("/users")
.then()
.statusCode(is(oneOf(200, 201)));
}
@Test
public void testHeaders() {
given()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.get("/users/1")
.then()
.header("Content-Type", containsString("application/json"))
.header("Server", notNullValue());
}
@Test
public void testBodyValidation() {
given()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.get("/users/1")
.then()
.statusCode(200)
// Single field validation
.body("name", equalTo("Leanne Graham"))
.body("id", is(1))
.body("email", containsString("@"))
// Nested field validation
.body("address.city", equalTo("Gwenborough"))
.body("address.geo.lat", equalTo("-37.3159"))
// Multiple validations
.body("username", notNullValue())
.body("phone", not(emptyString()));
}
@Test
public void testCollectionValidation() {
given()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.get("/users")
.then()
.statusCode(200)
// Array size
.body("size()", equalTo(10))
// Check if array contains
.body("name", hasItem("Leanne Graham"))
// All items match condition
.body("id", everyItem(greaterThan(0)))
// Specific index
.body("[0].name", equalTo("Leanne Graham"))
.body("[0].id", is(1))
// Find and validate
.body("find { it.id == 1 }.name", equalTo("Leanne Graham"));
}
@Test
public void testResponseTime() {
given()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.get("/users/1")
.then()
.time(lessThan(2000L)); // Less than 2 seconds
}
REST Assured supports various authentication mechanisms.
@Test
public void testBasicAuth() {
given()
.baseUri("https://httpbin.org")
.auth()
.basic("username", "password")
.when()
.get("/basic-auth/username/password")
.then()
.statusCode(200)
.body("authenticated", equalTo(true));
}
@Test
public void testBearerToken() {
String token = "your-jwt-token-here";
given()
.baseUri("https://api.example.com")
.header("Authorization", "Bearer " + token)
.when()
.get("/protected-resource")
.then()
.statusCode(200);
}
// Alternative method
@Test
public void testBearerTokenAlt() {
given()
.baseUri("https://api.example.com")
.auth()
.oauth2("your-jwt-token-here")
.when()
.get("/protected-resource")
.then()
.statusCode(200);
}
@Test
public void testOAuth2() {
// Using pre-obtained access token
String accessToken = "your-access-token";
given()
.baseUri("https://api.example.com")
.auth()
.oauth2(accessToken)
.when()
.get("/user/profile")
.then()
.statusCode(200);
}
@Test
public void testApiKeyInHeader() {
given()
.baseUri("https://api.example.com")
.header("X-API-Key", "your-api-key-here")
.when()
.get("/data")
.then()
.statusCode(200);
}
@Test
public void testApiKeyInQueryParam() {
given()
.baseUri("https://api.example.com")
.queryParam("api_key", "your-api-key-here")
.when()
.get("/data")
.then()
.statusCode(200);
}
@Test
public void testHeaders() {
given()
.baseUri("https://jsonplaceholder.typicode.com")
// Single header
.header("Content-Type", "application/json")
// Multiple headers
.header("Accept", "application/json")
.header("User-Agent", "RestAssured-Tests")
// Or using headers() for multiple
.headers("Accept", "application/json",
"User-Agent", "RestAssured-Tests")
.when()
.get("/users/1")
.then()
.statusCode(200);
}
@Test
public void testExtractHeaders() {
Response response = given()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.get("/users/1")
.then()
.extract()
.response();
String contentType = response.getHeader("Content-Type");
String server = response.getHeader("Server");
System.out.println("Content-Type: " + contentType);
System.out.println("Server: " + server);
}
@Test
public void testCookies() {
// Send cookie with request
given()
.baseUri("https://httpbin.org")
.cookie("session_id", "abc123xyz")
.when()
.get("/cookies")
.then()
.statusCode(200)
.body("cookies.session_id", equalTo("abc123xyz"));
}
@Test
public void testExtractCookies() {
Response response = given()
.baseUri("https://httpbin.org")
.when()
.get("/cookies/set?name=value")
.then()
.extract()
.response();
String cookieValue = response.getCookie("name");
System.out.println("Cookie value: " + cookieValue);
}
Extract and validate complex JSON structures using JSON Path.
@Test
public void testJsonPath() {
given()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.get("/users")
.then()
.statusCode(200)
// Root level array size
.body("$", hasSize(10))
// Get all names
.body("name", hasSize(10))
// Get first user's name
.body("[0].name", equalTo("Leanne Graham"))
// Filter: Find user with specific ID
.body("find { it.id == 5 }.name", equalTo("Chelsey Dietrich"))
// findAll: Get all users with id > 5
.body("findAll { it.id > 5 }.name",
hasItems("Mrs. Dennis Schulist", "Kurtis Weissnat"))
// Nested path
.body("[0].address.city", equalTo("Gwenborough"))
// Multiple nested levels
.body("[0].address.geo.lat", equalTo("-37.3159"))
// Collect: Get all emails
.body("collect { it.email }",
hasItem("Sincere@april.biz"));
}
| Operator | Description | Example |
|---|---|---|
$ |
Root element | $ or $.users |
. |
Child element | user.name |
[n] |
Array index | users[0] |
[*] |
All elements | users[*].name |
find |
Find first match | find { it.id == 1 } |
findAll |
Find all matches | findAll { it.age > 18 } |
Reuse common configurations across multiple tests using specifications.
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
public class APITests {
private RequestSpecification requestSpec;
private ResponseSpecification responseSpec;
@BeforeClass
public void setup() {
// Build Request Specification
requestSpec = new RequestSpecBuilder()
.setBaseUri("https://jsonplaceholder.typicode.com")
.setContentType("application/json")
.addHeader("Accept", "application/json")
.build();
// Build Response Specification
responseSpec = new ResponseSpecBuilder()
.expectStatusCode(200)
.expectContentType("application/json")
.expectResponseTime(lessThan(3000L))
.build();
}
@Test
public void testWithSpec() {
given()
.spec(requestSpec)
.when()
.get("/users/1")
.then()
.spec(responseSpec)
.body("name", equalTo("Leanne Graham"));
}
@Test
public void testAnotherEndpoint() {
given()
.spec(requestSpec)
.when()
.get("/users/2")
.then()
.spec(responseSpec)
.body("name", equalTo("Ervin Howell"));
}
}
import java.io.File;
@Test
public void testFileUpload() {
File file = new File("src/test/resources/test-file.txt");
given()
.baseUri("https://httpbin.org")
.multiPart("file", file)
.when()
.post("/post")
.then()
.statusCode(200)
.body("files", notNullValue());
}
@Test
public void testMultipleFilesUpload() {
File file1 = new File("src/test/resources/file1.txt");
File file2 = new File("src/test/resources/file2.txt");
given()
.baseUri("https://httpbin.org")
.multiPart("file1", file1)
.multiPart("file2", file2)
.when()
.post("/post")
.then()
.statusCode(200);
}
@Test
public void testFileDownload() {
byte[] fileData = given()
.baseUri("https://httpbin.org")
.when()
.get("/image/png")
.then()
.statusCode(200)
.extract()
.asByteArray();
// Save to file
try {
Files.write(Paths.get("downloaded-image.png"), fileData);
System.out.println("File downloaded successfully");
} catch (IOException e) {
e.printStackTrace();
}
}
Validate entire JSON structure against a schema to ensure API contract compliance.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"email": {
"type": "string",
"format": "email"
},
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" }
},
"required": ["street", "city"]
}
},
"required": ["id", "name", "email"]
}
import static io.restassured.module.jsv.JsonSchemaValidator.*;
@Test
public void testJsonSchemaValidation() {
given()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.get("/users/1")
.then()
.statusCode(200)
.body(matchesJsonSchemaInClasspath("user-schema.json"));
}
@Test
public void testArraySchemaValidation() {
given()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.get("/users")
.then()
.statusCode(200)
.body(matchesJsonSchemaInClasspath("users-array-schema.json"));
}
REST Assured provides comprehensive logging options for debugging.
@Test
public void testLogging() {
// Log everything
given()
.log().all()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.get("/users/1")
.then()
.log().all()
.statusCode(200);
// Log only if validation fails
given()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.get("/users/1")
.then()
.log().ifValidationFails()
.statusCode(200);
// Log only request/response body
given()
.log().body()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.get("/users/1")
.then()
.log().body()
.statusCode(200);
// Log only headers
given()
.log().headers()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.get("/users/1")
.then()
.log().headers()
.statusCode(200);
// Log only status line
given()
.baseUri("https://jsonplaceholder.typicode.com")
.when()
.get("/users/1")
.then()
.log().status()
.statusCode(200);
}
| Method | Description |
|---|---|
log().all() |
Log everything (request/response) |
log().body() |
Log only the body |
log().headers() |
Log only headers |
log().cookies() |
Log only cookies |
log().status() |
Log only status line |
log().ifValidationFails() |
Log only when test fails |
log().ifError() |
Log only if status code >= 400 |
import org.testng.annotations.*;
import io.restassured.specification.RequestSpecification;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
public class UserAPITests {
private RequestSpecification requestSpec;
@BeforeClass
public void setup() {
baseURI = "https://jsonplaceholder.typicode.com";
requestSpec = given()
.contentType("application/json")
.accept("application/json");
}
@Test(priority = 1)
public void testGetAllUsers() {
given()
.spec(requestSpec)
.when()
.get("/users")
.then()
.statusCode(200)
.body("$", hasSize(10));
}
@Test(priority = 2)
public void testGetUserById() {
given()
.spec(requestSpec)
.when()
.get("/users/1")
.then()
.statusCode(200)
.body("id", equalTo(1))
.body("name", notNullValue());
}
@Test(priority = 3, dataProvider = "userIds")
public void testMultipleUsers(int userId, String expectedName) {
given()
.spec(requestSpec)
.when()
.get("/users/" + userId)
.then()
.statusCode(200)
.body("name", equalTo(expectedName));
}
@DataProvider(name = "userIds")
public Object[][] getUserData() {
return new Object[][] {
{1, "Leanne Graham"},
{2, "Ervin Howell"},
{3, "Clementine Bauch"}
};
}
@Test(priority = 4)
public void testCreateUser() {
String newUser = """
{
"name": "Test User",
"email": "test@example.com"
}
""";
given()
.spec(requestSpec)
.body(newUser)
.when()
.post("/users")
.then()
.statusCode(201)
.body("name", equalTo("Test User"));
}
@AfterClass
public void tearDown() {
System.out.println("All tests completed");
}
}
public class DataDrivenTests {
@Test(dataProvider = "searchData")
public void testSearchWithDifferentParams(String query, int expectedResults) {
given()
.baseUri("https://jsonplaceholder.typicode.com")
.queryParam("userId", query)
.when()
.get("/posts")
.then()
.statusCode(200)
.body("size()", equalTo(expectedResults));
}
@DataProvider(name = "searchData")
public Object[][] getSearchData() {
return new Object[][] {
{"1", 10},
{"2", 10},
{"3", 10}
};
}
// Reading from CSV or Excel
@DataProvider(name = "usersFromFile")
public Object[][] getUsersFromFile() throws IOException {
// Read from CSV file
List
Create reusable RequestSpecification and ResponseSpecification to avoid code duplication.
Keep test data in separate files (JSON, CSV, Excel) or use DataProviders for maintainability.
Create Java classes for request/response bodies instead of string manipulation.
Use JSON Schema validation to ensure API contracts are maintained.
Use log().ifValidationFails() to reduce noise but capture failures.
Group related tests in classes, use meaningful test names, and set priorities.
Externalize base URIs and credentials using properties files or environment variables.
Use appropriate Hamcrest matchers for clear, readable assertions.
base.uri=https://jsonplaceholder.typicode.com
api.timeout=3000
auth.token=your-token-here
import java.io.FileInputStream;
import java.util.Properties;
public class ConfigReader {
private static Properties properties;
static {
try {
properties = new Properties();
FileInputStream fis = new FileInputStream(
"src/test/resources/config.properties"
);
properties.load(fis);
} catch (Exception e) {
e.printStackTrace();
}
}
public static String getProperty(String key) {
return properties.getProperty(key);
}
}
// Usage in tests
@BeforeClass
public void setup() {
RestAssured.baseURI = ConfigReader.getProperty("base.uri");
}
A full test suite demonstrating all major concepts:
import org.testng.annotations.*;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import io.restassured.builder.RequestSpecBuilder;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
public class CompleteAPITestSuite {
private RequestSpecification requestSpec;
private int createdUserId;
@BeforeClass
public void setupTestSuite() {
// Set base URI
baseURI = "https://jsonplaceholder.typicode.com";
// Build request specification
requestSpec = new RequestSpecBuilder()
.setContentType("application/json")
.addHeader("Accept", "application/json")
.build();
System.out.println("Test Suite Started");
}
@Test(priority = 1, description = "Verify API health")
public void testAPIHealth() {
given()
.spec(requestSpec)
.when()
.get("/users")
.then()
.statusCode(200)
.time(lessThan(3000L))
.body("$", hasSize(greaterThan(0)));
}
@Test(priority = 2, description = "Get all users and validate response")
public void testGetAllUsers() {
Response response = given()
.spec(requestSpec)
.when()
.get("/users")
.then()
.statusCode(200)
.body("size()", equalTo(10))
.body("name", everyItem(notNullValue()))
.body("email", everyItem(containsString("@")))
.extract()
.response();
System.out.println("Total users: " + response.jsonPath().getList("$").size());
}
@Test(priority = 3, description = "Get user by ID with validation")
public void testGetUserById() {
given()
.spec(requestSpec)
.pathParam("id", 1)
.when()
.get("/users/{id}")
.then()
.statusCode(200)
.body("id", equalTo(1))
.body("name", equalTo("Leanne Graham"))
.body("email", equalTo("Sincere@april.biz"))
.body("address.city", equalTo("Gwenborough"))
.body("address.geo.lat", equalTo("-37.3159"));
}
@Test(priority = 4, description = "Create new user")
public void testCreateUser() {
String newUser = """
{
"name": "Automation Test User",
"username": "testuser",
"email": "testuser@test.com",
"address": {
"street": "Test Street",
"city": "Test City",
"zipcode": "12345"
}
}
""";
Response response = given()
.spec(requestSpec)
.body(newUser)
.when()
.post("/users")
.then()
.statusCode(201)
.body("name", equalTo("Automation Test User"))
.body("email", equalTo("testuser@test.com"))
.extract()
.response();
createdUserId = response.path("id");
System.out.println("Created user with ID: " + createdUserId);
}
@Test(priority = 5, dependsOnMethods = "testCreateUser",
description = "Update user")
public void testUpdateUser() {
String updatedUser = """
{
"id": %d,
"name": "Updated User Name",
"email": "updated@test.com"
}
""".formatted(createdUserId);
given()
.spec(requestSpec)
.pathParam("id", createdUserId)
.body(updatedUser)
.when()
.put("/users/{id}")
.then()
.statusCode(200)
.body("name", equalTo("Updated User Name"))
.body("email", equalTo("updated@test.com"));
}
@Test(priority = 6, description = "Search posts by user ID")
public void testSearchPosts() {
given()
.spec(requestSpec)
.queryParam("userId", 1)
.when()
.get("/posts")
.then()
.statusCode(200)
.body("size()", equalTo(10))
.body("userId", everyItem(equalTo(1)))
.body("[0].title", notNullValue());
}
@Test(priority = 7, description = "Verify pagination")
public void testPagination() {
given()
.spec(requestSpec)
.queryParam("_page", 1)
.queryParam("_limit", 5)
.when()
.get("/posts")
.then()
.statusCode(200)
.body("size()", equalTo(5));
}
@Test(priority = 8, description = "Test error handling")
public void testNotFound() {
given()
.spec(requestSpec)
.when()
.get("/users/999999")
.then()
.statusCode(404);
}
@Test(priority = 9, dependsOnMethods = "testCreateUser",
description = "Delete user")
public void testDeleteUser() {
given()
.spec(requestSpec)
.pathParam("id", createdUserId)
.when()
.delete("/users/{id}")
.then()
.statusCode(200);
System.out.println("Deleted user with ID: " + createdUserId);
}
@Test(priority = 10, description = "Validate response time")
public void testResponseTime() {
long startTime = System.currentTimeMillis();
given()
.spec(requestSpec)
.when()
.get("/users")
.then()
.time(lessThan(2000L));
long endTime = System.currentTimeMillis();
System.out.println("Response time: " + (endTime - startTime) + "ms");
}
@AfterClass
public void tearDown() {
System.out.println("Test Suite Completed");
}
}