Back to guides

GUIDE / api

Rest Assured Tutorial: API Testing with Java from Scratch

Rest Assured tutorial for Java testers covering setup, requests, assertions, auth, JSON validation, framework design, and common mistakes.

By The Testing AcademyPublished July 10, 2026Updated July 10, 202614 min read

Rest Assured tutorial content often starts with a simple GET request and stops before the work becomes real. Real API testing with Rest Assured means more than sending a request. You need clean setup, reusable request specifications, strong assertions, authentication handling, schema validation, test data control, reporting, and a framework that can run reliably in CI.

This guide walks through Rest Assured from the viewpoint of a QA engineer building useful API automation in Java. You will learn what to test, how to structure requests, how to validate responses, when to use JSON schema checks, how to avoid brittle tests, and how Rest Assured fits into a larger API testing strategy.

Rest Assured Tutorial: What You Will Build

In this Rest Assured tutorial, you will build the foundation for a Java API automation suite that can send HTTP requests, validate responses, handle authentication, reuse configuration, and express business checks clearly. The goal is not a clever demo. The goal is a maintainable testing style you can use in a real project.

Rest Assured is a Java DSL for testing REST APIs. It lets you write tests that read close to HTTP language:

given()
    .baseUri("https://api.example.com")
    .header("Authorization", "Bearer " + token)
    .queryParam("status", "active")
.when()
    .get("/users")
.then()
    .statusCode(200)
    .body("data.size()", greaterThan(0));

The style is simple, but the power comes from how you organize it. A suite with copy-pasted base URLs, tokens, payloads, and assertions becomes painful quickly. A good suite separates configuration, request building, domain data, and assertions.

If you are new to the API testing discipline itself, read API testing tutorial first. Rest Assured is an automation tool. It does not replace understanding HTTP, contracts, data setup, status codes, authentication, and negative testing.

When to Use Rest Assured

Use Rest Assured when your team wants code-based API automation in Java. It is a strong fit for SDET teams, Java backend teams, and QA engineers who want tests in version control with CI execution.

Rest Assured is useful for:

  • REST API smoke tests.
  • Regression suites for stable endpoints.
  • Authentication and authorization checks.
  • JSON body assertions.
  • Header and cookie validation.
  • Contract and schema checks.
  • Backend test data setup for UI tests.
  • CI release gates.

Rest Assured is not the best tool for every job. Postman is often faster for exploration. Pact is better for consumer-driven contract testing. k6 is better for performance testing. WireMock is better for mocking dependencies. OpenAPI tools are better for validating documentation coverage. A strong API testing strategy uses the right layer for the risk.

Here is a practical comparison:

NeedBetter Starting Tool
Explore a new API manuallyPostman
Build Java regression automationRest Assured
Validate consumer/provider contractsPact
Mock unavailable dependenciesWireMock
Load test API throughputk6, JMeter, Gatling
Validate OpenAPI examples and schemaOpenAPI testing tools

Rest Assured shines when you want tests that behave like production clients and can be reviewed like code.

Project Setup

A common Java setup uses Maven or Gradle with JUnit 5 or TestNG. Maven is shown here because many QA teams start with it.

Example dependencies:

<dependencies>
  <dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <version>5.5.0</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest</artifactId>
    <version>2.2</version>
    <scope>test</scope>
  </dependency>
</dependencies>

Keep versions aligned with your project rules. If the repository already has dependency management, follow it. Do not introduce a separate testing stack without checking existing build conventions.

A basic folder structure might look like this:

src/test/java
  api/
    tests/
      UsersApiTest.java
      AuthApiTest.java
    clients/
      UsersClient.java
      AuthClient.java
    support/
      ApiConfig.java
      TokenProvider.java
      TestDataFactory.java
src/test/resources
  schemas/
    user-response-schema.json
  payloads/
    create-user.json

This is only a starting point. The key idea is separation. Test classes describe behavior. Client classes send requests. Support classes handle configuration, authentication, and data.

Your First Rest Assured Test

Start with a simple GET request. The test below verifies that an endpoint returns HTTP 200 and a JSON field.

import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;

class UsersApiTest {

    @Test
    void userCanBeFetchedById() {
        given()
            .baseUri("https://api.example.com")
        .when()
            .get("/users/123")
        .then()
            .statusCode(200)
            .body("id", equalTo(123))
            .body("status", equalTo("active"));
    }
}

This test is readable, but it is not a finished framework pattern. If every test repeats the base URI, headers, and auth, maintenance becomes expensive. The next step is reusable specifications.

Use Request and Response Specifications

Rest Assured lets you define request and response specifications. These keep common behavior in one place.

import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;

class ApiSpecs {
    static RequestSpecification baseRequest() {
        return new RequestSpecBuilder()
            .setBaseUri(System.getProperty("api.baseUrl", "https://api.example.com"))
            .addHeader("Accept", "application/json")
            .addHeader("Content-Type", "application/json")
            .build();
    }
}

Then use it:

given()
    .spec(ApiSpecs.baseRequest())
.when()
    .get("/health")
.then()
    .statusCode(200);

This improves consistency. It also makes environment switching easier. Your CI pipeline can pass -Dapi.baseUrl=https://staging-api.example.com without editing test code.

Response specifications can enforce common expectations:

static ResponseSpecification okJsonResponse() {
    return new ResponseSpecBuilder()
        .expectStatusCode(200)
        .expectHeader("Content-Type", containsString("application/json"))
        .build();
}

Use common specifications carefully. Not every endpoint should return the same headers, caching rules, or response time. Shared expectations are useful, but they should not hide meaningful differences.

Testing GET, POST, PUT, PATCH, and DELETE

Good API suites cover behavior, not only methods. Still, understanding each method helps structure tests.

MethodCommon UseTest Focus
GETRead dataStatus, filtering, pagination, fields, auth, not found
POSTCreate resourceRequired fields, validation, duplicate handling, generated IDs
PUTReplace resourceFull update behavior, missing fields, idempotency
PATCHPartial updateChanged fields, unchanged fields, validation
DELETERemove resourceStatus, repeated delete, authorization, downstream visibility

Example POST test:

@Test
void adminCanCreateUser() {
    String payload = """
        {
          "name": "QA User",
          "email": "qa.user@example.com",
          "role": "viewer"
        }
        """;

    given()
        .spec(ApiSpecs.authenticatedRequest())
        .body(payload)
    .when()
        .post("/users")
    .then()
        .statusCode(201)
        .body("email", equalTo("qa.user@example.com"))
        .body("role", equalTo("viewer"))
        .body("id", notNullValue());
}

This checks the response, but a stronger test may also fetch the created user afterward. Be careful with cleanup. Tests that create data should delete or isolate it unless the environment resets automatically.

Validating JSON Responses

Rest Assured supports JSON path expressions. This lets you validate nested fields, arrays, and computed conditions.

given()
    .spec(ApiSpecs.authenticatedRequest())
.when()
    .get("/orders")
.then()
    .statusCode(200)
    .body("orders.size()", greaterThan(0))
    .body("orders[0].total.currency", equalTo("USD"))
    .body("orders.findAll { it.status == 'PAID' }.size()", greaterThan(0));

Use JSON path for checks that matter. Avoid asserting every field in every response unless the test is a contract or schema test. Over-assertion makes tests brittle when harmless fields change.

A useful pattern is to combine:

  • Status code assertion.
  • One or two business field assertions.
  • Schema validation for structure.
  • Optional database or follow-up API verification when needed.

For deeper schema strategy, read JSON schema validation in API testing. Schema checks are powerful, but they should be paired with behavior checks.

Authentication in Rest Assured

Most real APIs are protected. Rest Assured can attach tokens, API keys, cookies, and headers.

Example bearer token:

given()
    .spec(ApiSpecs.baseRequest())
    .header("Authorization", "Bearer " + token)
.when()
    .get("/account")
.then()
    .statusCode(200);

In a framework, do not copy token logic into every test. Create a token provider:

class TokenProvider {
    String tokenFor(String username, String password) {
        return given()
            .spec(ApiSpecs.baseRequest())
            .body(Map.of("username", username, "password", password))
        .when()
            .post("/auth/token")
        .then()
            .statusCode(200)
            .extract()
            .path("access_token");
    }
}

Then your request spec can include auth:

static RequestSpecification authenticatedRequest() {
    String token = new TokenProvider().tokenFor("admin", "secret");

    return new RequestSpecBuilder()
        .setBaseUri(ApiConfig.baseUrl())
        .addHeader("Authorization", "Bearer " + token)
        .addHeader("Content-Type", "application/json")
        .build();
}

In real projects, avoid hardcoding credentials. Use CI secrets, environment variables, or secure configuration. Also test negative authentication cases: missing token, expired token, invalid signature, wrong role, and revoked access. The guide on API authentication testing covers those cases in detail.

Negative Testing and Error Assertions

API automation becomes valuable when it validates failure behavior, not only happy paths. Negative tests catch broken validation, unsafe defaults, and inconsistent error contracts.

Test these categories:

  • Missing required field.
  • Invalid field type.
  • Invalid enum value.
  • Boundary value exceeded.
  • Duplicate resource.
  • Unauthorized request.
  • Forbidden role.
  • Unknown resource ID.
  • Unsupported method.
  • Unsupported content type.
  • Malformed JSON.

Example:

@Test
void createUserRejectsMissingEmail() {
    String payload = """
        {
          "name": "No Email User",
          "role": "viewer"
        }
        """;

    given()
        .spec(ApiSpecs.authenticatedRequest())
        .body(payload)
    .when()
        .post("/users")
    .then()
        .statusCode(400)
        .body("error.code", equalTo("VALIDATION_ERROR"))
        .body("error.fields.email", containsString("required"));
}

Good error assertions are specific enough to catch contract changes, but not so specific that copy edits break the suite. Assert stable codes and field names. Be cautious with full message text unless exact text is part of the public contract.

Building a Rest Assured Framework

A useful framework grows from repeated pain. Do not start by creating twenty layers. Start simple, then extract what repeats.

Core components:

ComponentResponsibility
ConfigBase URL, environment, timeouts, feature flags
Request specsCommon headers, auth, content type, logging
API clientsOne class per API domain, such as users or orders
Test data factoryValid and invalid payload builders
AssertionsShared business and contract assertions
Schema filesJSON schemas for response structure
ReportingRequest and response evidence on failure
CleanupDelete created data or reset test state

The client layer is important. Instead of writing raw Rest Assured calls in every test, create readable domain methods:

class UsersClient {
    Response createUser(Map<String, Object> payload) {
        return given()
            .spec(ApiSpecs.authenticatedRequest())
            .body(payload)
        .when()
            .post("/users");
    }

    Response getUser(String userId) {
        return given()
            .spec(ApiSpecs.authenticatedRequest())
        .when()
            .get("/users/{id}", userId);
    }
}

Then tests read like behavior:

@Test
void createdUserCanBeFetched() {
    Map<String, Object> user = TestDataFactory.validUser();

    String id = usersClient.createUser(user)
        .then()
        .statusCode(201)
        .extract()
        .path("id");

    usersClient.getUser(id)
        .then()
        .statusCode(200)
        .body("email", equalTo(user.get("email")));
}

This structure is easier to maintain and review than scattered request code.

For broader framework decisions, read API automation framework. Rest Assured is one tool inside that framework.

Common Mistakes in Rest Assured API Testing

The first mistake is using Rest Assured before understanding the API. A tester should know the endpoint purpose, input rules, response contract, auth model, status codes, and data dependencies before automating.

The second mistake is copy-pasting request setup. Base URLs, headers, tokens, and content types should be centralized. Copy-paste makes environment changes painful and creates inconsistent behavior.

The third mistake is asserting too little. A test that checks only status code 200 may pass while the API returns wrong data. Include meaningful business assertions.

The fourth mistake is asserting too much. Checking every response field in every test creates noise. Save exhaustive structure checks for schema or contract tests.

The fifth mistake is depending on test order. API tests should create or locate their own data where possible. A test suite that passes only when run from top to bottom is fragile.

The sixth mistake is ignoring cleanup. Data pollution creates flaky failures, duplicate conflicts, and unreliable environments. Use unique test data and cleanup hooks.

The seventh mistake is hiding request and response details. When a CI failure happens, the report should show enough evidence to debug quickly. Log sensitive data carefully, but do not leave failures blind.

The eighth mistake is mixing performance expectations into every functional test. Response time assertions can be useful, but strict timing checks in shared CI often create noise. Use dedicated performance tests for serious latency work.

Rest Assured in CI/CD

Rest Assured tests become most valuable when they run automatically. Start with a smoke suite that runs on every deployment to a test environment. Add broader regression nightly or before release.

CI execution should include:

  • Environment URL passed by configuration.
  • Secrets injected securely.
  • Test tags for smoke, regression, contract, and destructive tests.
  • Parallel execution only when data isolation is ready.
  • HTML or XML reports.
  • Request and response logging on failure.
  • Clear build failure rules.

Do not run destructive tests against shared environments unless the team agrees. Delete tests, admin actions, and billing operations need explicit safeguards.

Use tags:

@Tag("smoke")
@Test
void healthEndpointIsAvailable() {
    given()
        .spec(ApiSpecs.baseRequest())
    .when()
        .get("/health")
    .then()
        .statusCode(200);
}

Then CI can run mvn test -Dgroups=smoke or a JUnit tag equivalent depending on your setup.

Designing Test Data for Rest Assured Suites

Rest Assured tests are only as reliable as the data behind them. Many beginner suites pass locally because the tester uses one familiar account, then fail in CI because that account has changed state. Avoid that pattern early.

Use unique data for resources that can collide. Email addresses, usernames, order numbers, coupon codes, and external IDs should usually include a timestamp, UUID, or test run identifier. This prevents duplicate conflicts when tests run repeatedly or in parallel. Keep the uniqueness readable enough for debugging. A value such as qa-user-20260710-153000@example.com is easier to investigate than a completely random string.

Use known reference data for stable domain values. Countries, currencies, product categories, permissions, and plan codes may already exist in the environment. Store those values in configuration or fixture files, not scattered through tests.

Use cleanup when created resources affect later tests. If your API supports deletion, delete resources after the test. If deletion is not allowed, create data under a disposable tenant or mark records with a test prefix so they can be cleaned by a scheduled job. If the system is event-driven and cleanup is eventually consistent, wait for the cleanup condition before ending the test or design tests so leftover data cannot affect the next run.

Also separate test data builders from test assertions. A builder creates valid or invalid payloads. The test explains why that payload matters. This keeps the intent visible:

Map<String, Object> payload = UserPayload.validViewer()
    .withEmail(uniqueEmail())
    .withoutPhoneNumber()
    .build();

That line is easier to review than a fifty-line JSON string copied into a test method.

Debugging Rest Assured Failures

Good Rest Assured suites make failure investigation fast. When a test fails, capture the request method, endpoint, sanitized headers, request body, response status, response body, environment, and correlation ID. The correlation ID is especially useful when developers need to find server logs.

Rest Assured supports conditional logging:

given()
    .spec(ApiSpecs.authenticatedRequest())
    .log().ifValidationFails()
.when()
    .get("/users/{id}", userId)
.then()
    .log().ifValidationFails()
    .statusCode(200);

Be careful with sensitive data. Tokens, passwords, identity numbers, personal addresses, and payment data should be redacted. A framework should make safe logging the default.

When a test fails, classify the failure before fixing anything:

Failure PatternLikely Cause
401 or 403 across many testsToken, role, or environment config issue
404 for created resourceData setup failed or eventual consistency not handled
Schema failure after deploymentAPI contract changed
Random timeoutEnvironment instability or dependency issue
One business assertion failsProduct behavior or test expectation changed

This classification prevents panic edits. Do not weaken assertions just to turn the build green. First decide whether the test caught a real defect, a contract change, a data problem, or a framework issue.

What to Practice Next

To become strong with Rest Assured, practice small but realistic tasks:

  • Test a public API with GET and POST.
  • Add request specifications.
  • Validate JSON body fields.
  • Add schema validation.
  • Implement bearer token auth.
  • Create a data factory.
  • Split raw calls into client classes.
  • Run the suite in CI.
  • Add negative tests for validation and authorization.

You can practice API reasoning in the QABattle testing battles. Treat each API challenge like a production risk: what input matters, what response proves behavior, what failure mode should be impossible, and what assertion gives useful evidence?

Rest Assured is not magic. It is a clean Java interface over HTTP testing. The value comes from how carefully you choose scenarios, isolate data, write assertions, and report failures. If your tests explain product behavior and fail for useful reasons, your Rest Assured suite is doing its job.

Keep the suite small at first, but make every test trustworthy. Before adding more cases, ask whether the next test protects a real user flow, a contract rule, a security boundary, or a defect that could return.

FAQ

Questions testers ask

What is Rest Assured used for?

Rest Assured is a Java library used to automate REST API testing. Testers use it to send HTTP requests, validate status codes, headers, JSON bodies, authentication behavior, response time, schema rules, and business assertions inside repeatable automated test suites.

Is Rest Assured good for beginners?

Rest Assured is beginner-friendly if you already know basic Java, HTTP methods, status codes, JSON, and test assertions. Beginners should first learn API testing concepts, then use Rest Assured to automate stable checks with clear setup, assertions, and reporting.

Which is better, Postman or Rest Assured?

Postman is better for exploring APIs, sharing collections, and quick manual checks. Rest Assured is better for code-based automation, version control, CI execution, reusable fixtures, and deeper framework design. Many teams use both at different stages.

Can Rest Assured test authentication?

Yes. Rest Assured can test API keys, bearer tokens, OAuth flows, basic auth, cookies, and custom headers. For complex authentication, teams usually create helper methods that request tokens, refresh them, and attach them consistently to protected API calls.

Do I need a framework for Rest Assured?

Small experiments can use plain test classes, but real projects need a framework structure for configuration, request builders, test data, response models, schema validation, reporting, environment handling, and reusable authentication helpers.