REST Assured Tutorial: Getting Started with Java API Testing

REST Assured Tutorial: Getting Started with Java API Testing

If you write Java and need to test REST APIs, REST Assured is the most popular library for the job. It lets you write readable, expressive HTTP tests that fit naturally into a JUnit or TestNG test suite. This tutorial walks you through everything from initial setup to writing your first meaningful API test.

What Is REST Assured?

REST Assured is an open-source Java library for testing and validating REST APIs. It provides a domain-specific language (DSL) built on top of Apache HTTP Client, giving you a fluent, human-readable syntax for constructing HTTP requests and asserting on responses.

Instead of this:

HttpClient client = HttpClients.createDefault();
HttpGet request = new HttpGet("https://api.example.com/users/1");
HttpResponse response = client.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
assertEquals(200, statusCode);

You write this:

given()
  .when()
    .get("https://api.example.com/users/1")
  .then()
    .statusCode(200);

The difference in readability is significant, especially when your test suite grows to hundreds of scenarios.

Project Setup

Maven Dependency

Add REST Assured to your pom.xml:

<dependencies>
  <dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <version>5.4.0</version>
    <scope>test</scope>
  </dependency>

  <!-- JSON path support -->
  <dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>json-path</artifactId>
    <version>5.4.0</version>
    <scope>test</scope>
  </dependency>

  <!-- JUnit 5 -->
  <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.0</version>
    <scope>test</scope>
  </dependency>
</dependencies>

Gradle (if you prefer)

testImplementation 'io.rest-assured:rest-assured:5.4.0'
testImplementation 'io.rest-assured:json-path:5.4.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'

Static Imports

Add these static imports at the top of each test class to get the fluent DSL working without verbose prefixes:

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

Your First Test

Let's test the public JSONPlaceholder API, which is perfect for practice:

import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

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

public class UserApiTest {

    @BeforeAll
    static void setup() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
    }

    @Test
    void getUserReturns200() {
        given()
          .when()
            .get("/users/1")
          .then()
            .statusCode(200);
    }

    @Test
    void getUserReturnsCorrectName() {
        given()
          .when()
            .get("/users/1")
          .then()
            .statusCode(200)
            .body("name", equalTo("Leanne Graham"))
            .body("email", containsString("@"));
    }
}

The given()when()then() structure maps directly to how you think about an API test: arrange, act, assert. This makes tests easy to scan even for people who don't write Java regularly.

Setting a Base URI

Setting RestAssured.baseURI in a @BeforeAll method is the standard pattern so you don't repeat the host in every test:

@BeforeAll
static void configure() {
    RestAssured.baseURI = "https://api.yourapp.com";
    RestAssured.basePath = "/v1";
    RestAssured.port = 443;
}

You can also create a RequestSpecification to share common headers or authentication across tests:

RequestSpecification requestSpec = new RequestSpecBuilder()
    .setBaseUri("https://api.yourapp.com")
    .setBasePath("/v1")
    .addHeader("Accept", "application/json")
    .build();

Sending Request Bodies

For POST and PUT requests, you'll need to send a JSON body:

@Test
void createPost() {
    String requestBody = """
        {
          "title": "Hello World",
          "body": "This is a test post",
          "userId": 1
        }
        """;

    given()
      .header("Content-Type", "application/json")
      .body(requestBody)
    .when()
      .post("/posts")
    .then()
      .statusCode(201)
      .body("id", notNullValue())
      .body("title", equalTo("Hello World"));
}

You can also use a POJO and let REST Assured serialize it with Jackson or Gson:

public class Post {
    public String title;
    public String body;
    public int userId;
}

@Test
void createPostWithPojo() {
    Post post = new Post();
    post.title = "Hello World";
    post.body = "This is a test post";
    post.userId = 1;

    given()
      .contentType(ContentType.JSON)
      .body(post)
    .when()
      .post("/posts")
    .then()
      .statusCode(201);
}

Add Jackson to your dependencies if you go the POJO route:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
    <scope>test</scope>
</dependency>

Extracting Response Data

Sometimes you need the response value to use in a follow-up assertion or request:

@Test
void createAndFetchPost() {
    // Create a post and capture the returned ID
    int postId =
        given()
          .contentType(ContentType.JSON)
          .body("{\"title\":\"Test\",\"userId\":1}")
        .when()
          .post("/posts")
        .then()
          .statusCode(201)
          .extract()
          .path("id");

    // Now fetch it
    given()
      .when()
        .get("/posts/" + postId)
      .then()
        .statusCode(200);
}

You can also extract the full response object:

Response response = given()
    .when()
      .get("/users/1")
    .then()
      .extract()
      .response();

String email = response.jsonPath().getString("email");
int id = response.jsonPath().getInt("id");

Query Parameters and Path Parameters

@Test
void filterPostsByUser() {
    given()
      .queryParam("userId", 1)
    .when()
      .get("/posts")
    .then()
      .statusCode(200)
      .body("size()", greaterThan(0))
      .body("userId", everyItem(equalTo(1)));
}

@Test
void getSpecificPost() {
    int postId = 5;

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

Common Hamcrest Matchers

REST Assured uses Hamcrest for assertions. Here are the matchers you'll use most:

Matcher What it does
equalTo(value) Exact equality
notNullValue() Field exists and is not null
containsString("x") String contains substring
hasSize(n) Collection has exactly n items
greaterThan(n) Numeric comparison
everyItem(equalTo(x)) All list items match
hasItem(x) List contains item
isA(Type.class) Type check

Structuring Tests at Scale

As your test suite grows, a few patterns keep things maintainable:

Base test class — put shared configuration in one place:

public abstract class BaseApiTest {

    @BeforeAll
    static void globalSetup() {
        RestAssured.baseURI = System.getenv().getOrDefault(
            "API_BASE_URL", "https://api.staging.yourapp.com"
        );
        RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
    }
}

Request specifications — DRY up headers and auth:

protected RequestSpecification authenticatedRequest() {
    return given()
        .header("Authorization", "Bearer " + getToken())
        .contentType(ContentType.JSON);
}

Response specifications — share common response assertions:

ResponseSpecification okJsonResponse = new ResponseSpecBuilder()
    .expectStatusCode(200)
    .expectContentType(ContentType.JSON)
    .build();

When REST Assured Isn't Enough

REST Assured is excellent for developer-written API tests, but it assumes everyone on your team writes Java. If your QA engineers, product managers, or customer success team want to run API tests without touching code, that's where tools like HelpMeTest come in. HelpMeTest lets anyone write API test scenarios in plain English, no code required, with usage-based pricing at $0.003 per run. It's not a replacement for REST Assured in a Java shop — it's a complement that lets non-developers participate in API testing.

Logging and Debugging

When a test fails, you want to see exactly what was sent and received. REST Assured makes this easy:

// Log everything always
given()
  .log().all()
.when()
  .get("/users/1")
.then()
  .log().all()
  .statusCode(200);

// Log only on failure (recommended for CI)
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();

The second option is better for CI pipelines — it keeps output clean on green runs but gives you full detail when something breaks.

Conclusion

REST Assured gives Java teams a clean, readable way to write API tests that integrate naturally with JUnit, Maven, and CI pipelines. The fluent DSL reduces boilerplate and makes test intent obvious. Start with the setup above, get comfortable with given/when/then, and layer in request/response specifications as your suite grows.

The next step after mastering the basics is handling authentication — tokens, OAuth flows, and session management. That's worth a dedicated guide (and we have one in this series).

Read more

Start now free