REST Assured has become the de facto standard for API testing in the Java ecosystem, offering a fluent DSL that makes complex HTTP request validation readable and maintainable. According to the JetBrains State of Developer Ecosystem 2023, Java remains the top language for backend development at 45% adoption, and REST Assured is used by over 60% of Java projects with automated API tests. According to a study by ThoughtWorks Technology Radar, REST Assured has been consistently listed as a ‘Trial’ or ‘Adopt’ tool since 2015, reflecting its proven reliability in enterprise API testing. For Java QA engineers, REST Assured’s given-when-then BDD syntax, built-in JSON/XML path assertions, schema validation, and seamless integration with JUnit and TestNG make it the most productive choice for building comprehensive API test suites.
TL;DR: REST Assured uses given().when().then() BDD syntax for Java API testing. Core features: JSONPath/XPath assertions, request/response logging, authentication (basic, OAuth2, JWT), schema validation, and TestNG/JUnit integration. Start with: given().baseUri(“https://api.example.com”).when().get("/users").then().statusCode(200).body(“size()”, greaterThan(0)).
Why REST Assured for API Testing?
REST Assured bridges the gap between API testing and Java development, offering native integration with existing Java test frameworks and build tools:
- Java-native solution: Seamless integration with JUnit, TestNG, Maven, and Gradle
- BDD-style syntax: Given-When-Then structure for readable test scenarios
- Rich assertion library: Hamcrest matchers and JsonPath/XmlPath support
- Authentication handling: Built-in support for OAuth, Basic Auth, and custom schemes
- Specification reuse: Define common request/response specifications once
- Performance: Direct HTTP client implementation without browser overhead
REST Assured vs Other Java Testing Frameworks
| Feature | REST Assured | Apache HttpClient | OkHttp | Spring RestTemplate |
|---|---|---|---|---|
| Readability | Excellent (BDD) | Low | Medium | Medium |
| Learning Curve | Low | High | Medium | Low |
| Response Validation | Built-in | Manual | Manual | Manual |
| JSON/XML Parsing | Native | External libs | External libs | Jackson |
| Authentication | Comprehensive | Manual | Manual | Built-in |
| Test Framework Integration | Excellent | Manual | Manual | Good |
“REST Assured’s given-when-then syntax was revolutionary when it appeared — it made API tests readable by non-technical stakeholders and maintainable by developers, solving both the communication and code quality problems at once.” — Yuri Kan, Senior QA Lead
Getting Started with REST Assured
Maven Dependency
<dependencies>
<!-- REST Assured -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>
<!-- JSON Schema Validation -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>5.5.0</version>
<scope>test</scope>
</dependency>
<!-- JUnit (as discussed in [Allure Framework: Creating Beautiful Test Reports](/blog/allure-framework-reporting)) 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Gradle Configuration
dependencies {
testImplementation 'io.rest-assured:rest-assured:5.5.0'
testImplementation 'io.rest-assured:json-schema-validator:5.5.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}
test {
useJUnitPlatform()
}
Basic Request Patterns
Simple GET Request
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
public class BasicAPITests {
@Test
public void testGetUser() {
given()
.baseUri("https://api.example.com")
.header("Accept", "application/json")
.when()
.get("/users/1")
.then()
.statusCode(200)
.body("id", equalTo(1))
.body("email", notNullValue())
.body("name", containsString("John"));
}
}
POST Request with Body
@Test
public void testCreateUser() {
String requestBody = """
{
"name": "Jane Doe",
"email": "jane@example.com",
"role": "admin"
}
""";
given()
.contentType(ContentType.JSON)
.body(requestBody)
.when()
.post("/users")
.then()
.statusCode(201)
.body("id", notNullValue())
.body("name", equalTo("Jane Doe"))
.body("email", equalTo("jane@example.com"));
}
Using POJOs (Plain Old Java Objects)
// User model
public class User {
private String name;
private String email;
private String role;
// Constructors, getters, setters
public User(String name, String email, String role) {
this.name = name;
this.email = email;
this.role = role;
}
// Getters and setters omitted for brevity
}
@Test
public void testCreateUserWithPOJO() {
User newUser = new User("Alice Johnson", "alice@example.com", "user");
User createdUser =
given()
.contentType(ContentType.JSON)
.body(newUser)
.when()
.post("/users")
.then()
.statusCode(201)
.extract()
.as(User.class);
assertThat(createdUser.getName(), equalTo("Alice Johnson"));
assertThat(createdUser.getEmail(), equalTo("alice@example.com"));
}
Advanced Request Handling
Query Parameters and Path Parameters
@Test
public void testQueryParameters() {
given()
.queryParam("page", 2)
.queryParam("limit", 10)
.queryParam("sort", "name")
.when()
.get("/users")
.then()
.statusCode(200)
.body("data.size()", equalTo(10))
.body("pagination.page", equalTo(2));
}
@Test
public void testPathParameters() {
int userId = 123;
String resourceId = "abc-456";
given()
.pathParam("userId", userId)
.pathParam("resourceId", resourceId)
.when()
.get("/users/{userId}/resources/{resourceId}")
.then()
.statusCode(200)
.body("userId", equalTo(userId))
.body("resourceId", equalTo(resourceId));
}
Authentication Mechanisms
// Basic Authentication
@Test
public void testBasicAuth() {
given()
.auth().basic("username", "password")
.when()
.get("/protected")
.then()
.statusCode(200);
}
// Bearer Token
@Test
public void testBearerToken() {
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
given()
.auth().oauth2(token)
.when()
.get("/api/secure-endpoint")
.then()
.statusCode(200);
}
// Preemptive Authentication
@Test
public void testPreemptiveAuth() {
given()
.auth().preemptive().basic("admin", "admin123")
.when()
.get("/admin/dashboard")
.then()
.statusCode(200);
}
// Custom Header Authentication
@Test
public void testCustomHeaderAuth() {
given()
.header("X-API-Key", "your-api-key-here")
.header("X-Client-ID", "client-123")
.when()
.get("/api/data")
.then()
.statusCode(200);
}
Request and Response Specifications
Eliminate duplication with reusable specifications:
public class APITestBase {
protected static RequestSpecification requestSpec;
protected static ResponseSpecification responseSpec;
@BeforeAll
public static void setup() {
requestSpec = new RequestSpecBuilder()
.setBaseUri("https://api.example.com")
.setBasePath("/api/v1")
.addHeader("Accept", "application/json")
.addHeader("Content-Type", "application/json")
.setAuth(oauth2("access-token"))
.setContentType(ContentType.JSON)
.build();
responseSpec = new ResponseSpecBuilder()
.expectStatusCode(200)
.expectResponseTime(lessThan(2000L))
.expectHeader("Content-Type", containsString("json"))
.build();
RestAssured.requestSpecification = requestSpec;
RestAssured.responseSpecification = responseSpec;
}
}
// Using specifications
@Test
public void testWithSpecifications() {
given()
.spec(requestSpec)
.queryParam("filter", "active")
.when()
.get("/users")
.then()
.spec(responseSpec)
.body("data", notNullValue());
}
JSON Response Validation
JsonPath Queries
@Test
public void testComplexJSONValidation() {
given()
.get("/api/products")
.then()
.statusCode(200)
// Check array size
.body("products.size()", greaterThan(0))
// Check nested values
.body("products[0].name", notNullValue())
.body("products[0].price", greaterThan(0f))
// Find specific items
.body("products.findAll { it.category == 'electronics' }.size()", greaterThan(0))
// Check all items meet condition
.body("products.every { it.price > 0 }", equalTo(true))
// Sum values
.body("products.collect { it.price }.sum()", greaterThan(100f));
}
@Test
public void testNestedJSONPaths() {
given()
.get("/api/orders/123")
.then()
.body("order.id", equalTo(123))
.body("order.customer.name", equalTo("John Doe"))
.body("order.items.size()", equalTo(3))
.body("order.items[0].productId", notNullValue())
.body("order.shipping.address.city", equalTo("New York"))
.body("order.payment.status", equalTo("completed"));
}
Extracting Values for Reuse
@Test
public void testExtractAndReuse() {
// Extract single value
String userId =
given()
.body(newUser)
.when()
.post("/users")
.then()
.statusCode(201)
.extract()
.path("id");
// Use extracted value in subsequent request
given()
.pathParam("id", userId)
.when()
.get("/users/{id}")
.then()
.statusCode(200)
.body("id", equalTo(userId));
}
@Test
public void testExtractComplexResponse() {
Response response =
given()
.get("/api/products")
.then()
.statusCode(200)
.extract()
.response();
// Extract multiple values
List<String> productNames = response.jsonPath().getList("products.name");
List<Float> prices = response.jsonPath().getList("products.price", Float.class);
Map<String, Object> firstProduct = response.jsonPath().getMap("products[0]");
// Assertions on extracted data
assertThat(productNames, hasSize(greaterThan(0)));
assertThat(prices, everyItem(greaterThan(0f)));
assertThat(firstProduct, hasKey("id"));
}
JSON Schema Validation
import static io.restassured.module.jsv.JsonSchemaValidator.*;
@Test
public void testJSONSchemaValidation() {
given()
.get("/api/users/1")
.then()
.statusCode(200)
.body(matchesJsonSchemaInClasspath("schemas/user-schema.json"));
}
// user-schema.json
/*
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["id", "email", "name"],
"properties": {
"id": {
"type": "integer"
},
"email": {
"type": "string",
"format": "email"
},
"name": {
"type": "string",
"minLength": 1
},
"role": {
"type": "string",
"enum": ["admin", "user", "guest"]
}
}
}
*/
Data-Driven Testing
Parameterized Tests with JUnit 5
@ParameterizedTest
@CsvSource({
"1, John Doe, john@example.com",
"2, Jane Smith, jane@example.com",
"3, Bob Johnson, bob@example.com"
})
public void testMultipleUsers(int id, String name, String email) {
given()
.pathParam("id", id)
.when()
.get("/users/{id}")
.then()
.statusCode(200)
.body("name", equalTo(name))
.body("email", equalTo(email));
}
@ParameterizedTest
@MethodSource("provideUserData")
public void testUserCreation(User user) {
given()
.contentType(ContentType.JSON)
.body(user)
.when()
.post("/users")
.then()
.statusCode(201)
.body("email", equalTo(user.getEmail()));
}
private static Stream<User> provideUserData() {
return Stream.of(
new User("Alice", "alice@test.com", "user"),
new User("Bob", "bob@test.com", "admin"),
new User("Charlie", "charlie@test.com", "moderator")
);
}
TestNG DataProvider
@DataProvider(name = "userData")
public Object[][] userData() {
return new Object[][] {
{"admin", "admin@example.com", 201},
{"user", "user@example.com", 201},
{"", "invalid", 400} // Negative test case
};
}
@Test(dataProvider = "userData")
public void testUserCreationWithDataProvider(String name, String email, int expectedStatus) {
User user = new User(name, email, "user");
given()
.contentType(ContentType.JSON)
.body(user)
.when()
.post("/users")
.then()
.statusCode(expectedStatus);
}
File Upload and Download
File Upload
@Test
public void testFileUpload() {
File imageFile = new File("src/test/resources/test-image.jpg");
given()
.multiPart("file", imageFile, "image/jpeg")
.multiPart("description", "Test image upload")
.when()
.post("/api/upload")
.then()
.statusCode(200)
.body("filename", equalTo("test-image.jpg"))
.body("size", greaterThan(0));
}
@Test
public void testMultipleFileUpload() {
File file1 = new File("src/test/resources/doc1.pdf");
File file2 = new File("src/test/resources/doc2.pdf");
given()
.multiPart("files", file1)
.multiPart("files", file2)
.when()
.post("/api/upload/multiple")
.then()
.statusCode(200)
.body("uploaded.size()", equalTo(2));
}
File Download
@Test
public void testFileDownload() {
byte[] fileContent =
given()
.pathParam("fileId", "abc123")
.when()
.get("/api/download/{fileId}")
.then()
.statusCode(200)
.header("Content-Type", "application/pdf")
.extract()
.asByteArray();
assertThat(fileContent.length, greaterThan(0));
// Save to file
File downloadedFile = new File("target/downloaded.pdf");
Files.write(downloadedFile.toPath(), fileContent);
}
Performance and Response Time Testing
@Test
public void testResponseTime() {
given()
.get("/api/products")
.then()
.statusCode(200)
.time(lessThan(500L), TimeUnit.MILLISECONDS);
}
@Test
public void testPerformanceMetrics() {
long start = System.currentTimeMillis();
Response response =
given()
.get("/api/heavy-operation")
.then()
.statusCode(200)
.extract()
.response();
long responseTime = response.getTime();
long totalTime = System.currentTimeMillis() - start;
assertThat(responseTime, lessThan(1000L));
assertThat(totalTime, lessThan(1500L));
System.out.println("Response time: " + responseTime + "ms");
System.out.println("Total time: " + totalTime + "ms");
}
CI/CD Integration
Maven Surefire Configuration
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<includes>
<include>**/*Test.java</include>
<include>**/*Tests.java</include>
</includes>
<systemPropertyVariables>
<api.base.uri>${env.API_BASE_URI}</api.base.uri>
<api.token>${env.API_TOKEN}</api.token>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
Jenkins Pipeline
pipeline {
agent any
environment {
API_BASE_URI = credentials('api-base-uri')
API_TOKEN = credentials('api-token')
}
stages {
stage('Checkout') {
steps {
git 'https://github.com/yourorg/api-tests.git'
}
}
stage('API Tests') {
steps {
sh 'mvn clean test -Dapi.base.uri=${API_BASE_URI} -Dapi.token=${API_TOKEN}'
}
}
stage('Publish Reports') {
steps {
junit '**/target/surefire-reports/*.xml'
publishHTML([
reportDir: 'target/site/serenity',
reportFiles: 'index.html',
reportName: 'API Test Report'
])
}
}
}
}
GitHub Actions
name: API Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: 'maven'
- name: Run API Tests
env:
API_BASE_URI: ${{ secrets.API_BASE_URI }}
API_TOKEN: ${{ secrets.API_TOKEN }}
run: mvn clean test
- name: Publish Test Results
uses: dorny/test-reporter@v1
if: always()
with:
name: API Test Results
path: target/surefire-reports/*.xml
reporter: java-junit
Best Practices
Organize Tests with Page Object Pattern
// UserEndpoint.java
public class UserEndpoint {
private static final String BASE_PATH = "/users";
public Response getAllUsers() {
return given()
.spec(APITestBase.requestSpec)
.when()
.get(BASE_PATH)
.then()
.extract()
.response();
}
public Response getUser(int userId) {
return given()
.spec(APITestBase.requestSpec)
.pathParam("id", userId)
.when()
.get(BASE_PATH + "/{id}")
.then()
.extract()
.response();
}
public Response createUser(User user) {
return given()
.spec(APITestBase.requestSpec)
.body(user)
.when()
.post(BASE_PATH)
.then()
.extract()
.response();
}
}
// Test class
public class UserAPITest {
private UserEndpoint userEndpoint = new UserEndpoint();
@Test
public void testGetAllUsers() {
Response response = userEndpoint.getAllUsers();
assertThat(response.statusCode(), equalTo(200));
}
}
Logging and Debugging
@Test
public void testWithLogging() {
given()
.log().all() // Log request
.get("/users")
.then()
.log().all() // Log response
.statusCode(200);
}
@Test
public void testConditionalLogging() {
given()
.log().ifValidationFails()
.get("/users")
.then()
.log().ifError()
.statusCode(200);
}
Conclusion
REST Assured provides a powerful, Java-native solution for API testing that integrates seamlessly into existing development workflows. Its BDD-style (as discussed in BDD: From Requirements to Automation) syntax, comprehensive validation capabilities, and rich ecosystem make it ideal for teams already invested in the Java stack.
Key advantages:
- Fluent, readable test syntax
- Deep integration with Java testing ecosystem
- Powerful JSON/XML validation
- Built-in authentication support
- Excellent CI/CD integration
- Active community and extensive documentation
Whether building microservices, testing REST APIs, or validating complex integration scenarios, REST Assured delivers the tools needed for comprehensive, maintainable API test automation.
FAQ
Is REST Assured only for REST APIs or can it test other protocols?
REST Assured is designed primarily for RESTful HTTP APIs. It supports GET, POST, PUT, DELETE, PATCH, and other HTTP methods with full control over headers, query parameters, and request bodies. For GraphQL APIs, you can send POST requests with query payloads. For SOAP services, you can use XML payloads. However, for gRPC or WebSocket testing, you need different tools like grpcurl or dedicated WebSocket clients.
How does REST Assured compare to Postman for API testing?
REST Assured is a Java library for programmatic, code-based API testing that integrates with JUnit and TestNG for CI/CD pipelines. Postman is a GUI-based tool better for manual exploration and quick ad-hoc testing. REST Assured excels at automated regression suites with data-driven testing, while Postman is better for initial API discovery and documentation. Many teams use both — Postman for exploration, REST Assured for automation.
What is the best way to handle authentication tokens in REST Assured tests?
Use a @BeforeAll or @BeforeClass method to obtain the token once and store it in a RequestSpecification. This avoids repeated authentication calls for every test. For OAuth2, use the built-in auth().oauth2(token) method. For API keys, use headers. Store credentials in environment variables or configuration files, never hardcode them in test code.
Can REST Assured validate JSON schema and what are the benefits?
Yes, REST Assured has a dedicated json-schema-validator module. Add the dependency and use matchesJsonSchemaInClasspath() to validate responses against JSON schema files stored in your test resources. This catches structural contract violations early — missing fields, wrong types, or unexpected values — before they reach production, serving as a lightweight form of contract testing.
Official Resources
See Also
- Jest & Testing Library: Modern Component Testing for React Applications
- Jest & Testing Library: Modern Component Testing for React Applications - Master Jest and Testing Library for React component testing. Learn best…
- Master Jest and Testing Library for React component testing. Learn…
- API Testing Mastery: From REST to Contract Testing - Complete guide to API testing: REST vs GraphQL vs gRPC, mastering…
- K6: Modern Load Testing with JavaScript for DevOps Teams - Master K6 for modern performance testing: JavaScript-based load…
- Artillery Performance Testing: Modern Load Testing with YAML Scenarios - Complete Artillery performance testing guide covering YAML…
