for an anonymous root array or a field path for nested data. Pass a Hamcrest matcher such as `hasSize`, `contains`, or `containsInAnyOrder` based on the API contract."}},{"@type":"Question","name":"What is the difference between hasItems and containsInAnyOrder?","acceptedAnswer":{"@type":"Answer","text":"`hasItems` requires the named values to occur but allows extras. `containsInAnyOrder` requires the complete multiset of values, including duplicate counts, while ignoring their order."}},{"@type":"Question","name":"How can I verify array order?","acceptedAnswer":{"@type":"Answer","text":"Use Hamcrest `contains` against the whole array or a projected field list. It fails when the values, positions, or number of occurrences differ."}},{"@type":"Question","name":"How do I check every object in a JSON array?","acceptedAnswer":{"@type":"Answer","text":"Select the object array and use `everyItem` with an object matcher such as `hasKey`. Add `hasSize` or another presence check if an empty result should fail."}},{"@type":"Question","name":"How do I assert an empty JSON array?","acceptedAnswer":{"@type":"Answer","text":"Use `body(\"$\", empty())` for an empty root array, or select the nested array path and apply `empty()`. Assert the HTTP status and content type as well so an error response does not masquerade as the expected state."}},{"@type":"Question","name":"Does REST Assured use Jayway JSONPath filters?","acceptedAnswer":{"@type":"Answer","text":"No. REST Assured's JSON path syntax is Groovy GPath. Use expressions such as `findAll { it.status == 'PAID' }.id` for filtering and projecting array elements."}},{"@type":"Question","name":"How can I check duplicate values in an array?","acceptedAnswer":{"@type":"Answer","text":"Use `contains` when order matters or `containsInAnyOrder` when it does not. Include each expected occurrence in the matcher arguments; `hasItems` alone does not enforce duplicate counts."}}]}]}
Resource library

QA How-To

REST Assured Hamcrest Matchers for JSON Arrays

Learn REST Assured Hamcrest matchers for JSON arrays with runnable Java tests for exact order, duplicates, nested objects, filters, empty lists, and nulls.

20 min read | 2,824 words

TL;DR

Select a JSON array with a REST Assured GPath expression, then apply Hamcrest to the selected list. Use hasItems for inclusion, contains for exact sequence, containsInAnyOrder for exact membership regardless of order, everyItem for a per-element rule, and hasSize or empty for cardinality.

Key Takeaways

  • Use hasItems for required values when additional array members are allowed.
  • Use contains for exact order and containsInAnyOrder for exact contents without an order requirement.
  • Pair everyItem with a nonempty assertion when the endpoint must return records.
  • Project and filter JSON array fields with REST Assured's Groovy GPath syntax.
  • Test duplicates, nested items, empty arrays, and explicit null elements separately.
  • Extract arrays into Java when an invariant spans multiple elements or pages.

REST Assured Hamcrest Matchers JSON Arrays is a search shorthand for a practical job: state whether an API returned the right members, order, count, and nested values without converting every response into a Java object. Use hasItems for required members, contains for exact order and multiplicity, containsInAnyOrder for exact membership when order is irrelevant, and everyItem for a rule that applies to each element. These assertions work against the values selected by REST Assured's Groovy GPath expressions.

This tutorial builds a self-contained Java test project with a local HTTP fixture. You will test a root JSON array of orders, arrays inside each order, duplicates, empty results, null elements, and filtered projections. If you are new to the request DSL, read the REST Assured given-when-then guide alongside the first test.

What You Will Build

  • A Maven project that runs JUnit Jupiter and REST Assured tests on Java 17.
  • A small in-process HTTP server with stable JSON responses, including nested arrays and edge cases.
  • Assertions for inclusion, exact contents, order, duplicates, predicates, and GPath filtering.
  • A final extraction test for an invariant that is clearer in Java than in a single matcher.

The local fixture is deliberate. Public demo APIs change records and ordering, which makes an array tutorial fail for reasons unrelated to the assertion. Here, each endpoint has a known contract. You can later replace baseUri with your service's test environment and keep the assertion patterns.

Prerequisites

Use JDK 17 and Apache Maven 3.9.11 for the commands below. The example pins REST Assured 6.0.1, JUnit Jupiter 6.1.1, Hamcrest 3.0, Maven Compiler Plugin 3.14.1, and Maven Surefire Plugin 3.5.5. These are published versions, not inferred version numbers. REST Assured 6 requires Java 17 or newer. If your team already has approved versions, match your installed toolchain and dependency policy before copying the POM. Check the REST Assured installation notes, JUnit Maven guidance, and Hamcrest releases when updating the pins.

Run java -version and mvn -version first. Confirm that Maven reports the same JDK you intend to compile with. A java command on one JDK and Maven on another can produce a confusing class-version error. Create an empty directory named array-matchers and run every following command from that directory. The examples use the default Java package to keep copy-and-run setup short; in a production project, move the classes into your normal test package.

Need Matcher or approach What a passing assertion proves
One or more required members hasItem, hasItems Named values occur at least once; extras are allowed
Exact sequence contains Values, order, and duplicate counts match
Exact bag of values containsInAnyOrder Values and duplicate counts match; order may differ
Rule for all members everyItem Each element satisfies the nested matcher
Size or no members hasSize, empty Cardinality matches the contract
Cross-element invariant Extract and assert in Java Relationship between elements holds

Step 1: Create the Maven Project

Create pom.xml with test-scoped dependencies. Put REST Assured before JUnit in the dependency list, following the REST Assured setup note about Hamcrest dependency selection. The explicit Hamcrest dependency fixes the matchers used by this tutorial. The compiler and test plugins are pinned so a fresh Maven installation does not silently choose a different plugin release.

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>example.qa</groupId>
  <artifactId>array-matchers</artifactId>
  <version>1.0-SNAPSHOT</version>
  <properties>
    <maven.compiler.release>17</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>io.rest-assured</groupId>
      <artifactId>rest-assured</artifactId>
      <version>6.0.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>6.1.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.hamcrest</groupId>
      <artifactId>hamcrest</artifactId>
      <version>3.0</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.14.1</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.5.5</version>
      </plugin>
    </plugins>
  </build>
</project>

Create the test source directory with mkdir -p src/test/java. Verify: run mvn -q test. Maven should exit successfully with no tests executed yet. A dependency-resolution error here is a repository or network issue, not a matcher failure. Keep the first green command as a baseline before adding the fixture.

Step 2: Serve Stable JSON Arrays Locally

Save the following as src/test/java/ArrayApi.java. The server binds to loopback on an ephemeral port, so parallel machines do not fight over a fixed port. It exposes /orders as a root array, /tags with a duplicate, /empty as an empty array, and /nullable with an explicit JSON null. Each response declares application/json, allowing REST Assured to select its JSON parser.

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;

final class ArrayApi implements AutoCloseable {
  private final HttpServer server;

  private ArrayApi(HttpServer server) {
    this.server = server;
  }

  static ArrayApi start() throws IOException {
    HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
    server.createContext("/orders", exchange -> reply(exchange, """
        [
          {"id":101,"status":"PAID","items":[{"sku":"PEN","quantity":2},{"sku":"BAG","quantity":1}]},
          {"id":102,"status":"PENDING","items":[{"sku":"NOTE","quantity":1}]},
          {"id":103,"status":"PAID","items":[{"sku":"PEN","quantity":1}]}
        ]
        """));
    server.createContext("/tags", exchange -> reply(exchange, "[\"gift\",\"priority\",\"gift\"]"));
    server.createContext("/empty", exchange -> reply(exchange, "[]"));
    server.createContext("/nullable", exchange -> reply(exchange, "[null,\"ready\"]"));
    server.start();
    return new ArrayApi(server);
  }

  String baseUri() {
    return "http://127.0.0.1:" + server.getAddress().getPort();
  }

  private static void reply(HttpExchange exchange, String json) throws IOException {
    byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
    exchange.getResponseHeaders().set("Content-Type", "application/json; charset=UTF-8");
    exchange.sendResponseHeaders(200, bytes.length);
    try (OutputStream out = exchange.getResponseBody()) {
      out.write(bytes);
    }
  }

  @Override
  public void close() {
    server.stop(0);
  }
}

The fixture is test code, not a replacement for testing your real API. Its purpose is to make the matchers observable: you know exactly which assertion should pass and how a changed response would fail. The try blocks in later tests call close() after each test, releasing the port even when an assertion fails. Verify: run mvn -q test again. Compilation should succeed; no test class uses the helper yet. If com.sun.net.httpserver is unavailable, verify that Maven is using a full JDK 17 installation rather than a stripped runtime.

Step 3: REST Assured Hamcrest Matchers JSON Arrays: Required Members

Save this test as src/test/java/MembershipTest.java. $ selects the whole root JSON array. The id expression projects the id field from each order into a list. hasItems(101, 103) proves those two IDs occur, but it intentionally says nothing about a third ID or ordering. everyItem checks the complete status projection, accepting exactly the two permitted status names at each position.

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.everyItem;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;

import org.junit.jupiter.api.Test;

class MembershipTest {
  @Test
  void requiredOrdersAndAllowedStatuses() throws Exception {
    try (ArrayApi api = ArrayApi.start()) {
      given().baseUri(api.baseUri())
          .when().get("/orders")
          .then().statusCode(200)
          .body("
quot;, hasSize(3), "id", hasItems(101, 103), "status", everyItem(anyOf(is("PAID"), is("PENDING")))); } } }

This is a good pattern for a search endpoint whose sort order is unspecified and where extra records are valid. It is a poor choice for an endpoint promising exactly three IDs: hasItems still passes if the API adds ID 999. Conversely, everyItem can pass vacuously on an empty list, so pair it with hasSize when at least one item is required. The first assertion does that here. Verify: run mvn -q -Dtest=MembershipTest test; expect exit code 0. Temporarily changing hasSize(3) to hasSize(4) should produce a clear assertion failure, then restore it.

Step 4: Distinguish Order from Exact Membership

Save src/test/java/OrderTest.java. The order endpoint promises ascending IDs, so contains tests the exact sequence. The status list has a repeated PAID value. containsInAnyOrder checks all three values, including both copies, without caring about order. On /tags, hasItems("gift", "priority") alone would pass for a response missing the second gift; exact membership catches that defect.

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasItems;

import org.junit.jupiter.api.Test;

class OrderTest {
  @Test
  void exactArrayContracts() throws Exception {
    try (ArrayApi api = ArrayApi.start()) {
      given().baseUri(api.baseUri())
          .when().get("/orders")
          .then().statusCode(200)
          .body("id", contains(101, 102, 103),
                "status", containsInAnyOrder("PAID", "PENDING", "PAID"));

      given().baseUri(api.baseUri())
          .when().get("/tags")
          .then().statusCode(200)
          .body("
quot;, hasItems("gift", "priority")) .body("
quot;, containsInAnyOrder("gift", "gift", "priority")); } } }

Use the least restrictive matcher that still states the contract. If the API deliberately returns the tags in ranking order, replace the final matcher with contains("gift", "priority", "gift"). If an endpoint only guarantees that selected tags occur somewhere, leave hasItems and remove the exact-content check. These are different business assertions, not interchangeable syntax. equalTo(List.of(...)) can also compare an exact Java list, but contains communicates positional intent more directly in a response assertion. Be careful when the endpoint returns a set-like collection: sorting it only inside the test can hide an ordering defect if the documented contract actually promises a sort. Instead, decide which guarantee the API makes and choose the matcher before seeing the current response. This prevents a test from simply codifying one sample payload. Verify: run mvn -q -Dtest=OrderTest test. To see the duplicate check work, remove one "gift" argument from containsInAnyOrder; the test should fail because the actual array has three elements.

Step 5: Filter Before You Match

A large response often contains mixed states. GPath can select just the relevant elements before Hamcrest compares them. Save src/test/java/FilterTest.java. findAll returns a list of the two paid orders, while find returns the first matching order. The dot after the closing brace projects fields from those results.

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.is;

import org.junit.jupiter.api.Test;

class FilterTest {
  @Test
  void paidOrderProjection() throws Exception {
    try (ArrayApi api = ArrayApi.start()) {
      given().baseUri(api.baseUri())
          .when().get("/orders")
          .then().statusCode(200)
          .body("findAll { it.status == 'PAID' }.id", containsInAnyOrder(101, 103),
                "findAll { it.status == 'PAID' }.size()", is(2),
                "find { it.id == 102 }.status", is("PENDING"));
    }
  }
}

REST Assured uses Groovy GPath for these JSON expressions, not Jayway JsonPath. That distinction matters when a familiar $.. filter expression fails to parse. The REST Assured usage guide documents findAll { ... } on arrays. Keep a filter close to the assertion it serves so reviewers can see whether it could accidentally hide a bad element. For instance, an assertion about all orders should project status directly, as Step 3 does, instead of filtering invalid statuses away first. Verify: run mvn -q -Dtest=FilterTest test; expect all three body checks to pass. Change 'PAID' to 'CANCELLED' temporarily and the expected IDs will no longer match.

Step 6: REST Assured Hamcrest Matchers JSON Arrays: Nested Objects

The first order has two items, each a JSON object. Save src/test/java/NestedItemsTest.java. The path [0].items selects the first order's nested array, and .sku projects its two SKU strings. everyItem(hasKey("sku")) checks structure at each item without requiring a JSON schema. A targeted find assertion links a particular SKU to its quantity.

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.everyItem;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;

import org.junit.jupiter.api.Test;

class NestedItemsTest {
  @Test
  void firstOrderLineItems() throws Exception {
    try (ArrayApi api = ArrayApi.start()) {
      given().baseUri(api.baseUri())
          .when().get("/orders")
          .then().statusCode(200)
          .body("[0].items", hasSize(2),
                "[0].items", everyItem(hasKey("sku")),
                "[0].items.sku", contains("PEN", "BAG"),
                "[0].items.find { it.sku == 'PEN' }.quantity", is(2));
    }
  }
}

The indexed path is suitable only if order position is part of the API contract. If the server is allowed to reorder orders, locate the order by ID first: find { it.id == 101 }.items.sku. Keep contains only when item ordering is also specified; otherwise use containsInAnyOrder. A hasKey assertion is intentionally narrow: it does not prove type, nullability, or allowed values. For a broad structural contract, pair focused matchers with REST Assured JSON schema validation. If line items can contain the same SKU more than once, decide whether duplicates represent separate lines or should be consolidated. A matcher on the SKU projection alone cannot tell you which quantity belongs to which duplicate. In that case, assert individual objects or extract the nested list and compare (sku, quantity) pairs. This avoids a false pass where the right SKUs appear but their quantities have been swapped. Verify: run mvn -q -Dtest=NestedItemsTest test; the assertion should pass with two items and quantity 2 for PEN.

Step 7: Handle Empty and Null Arrays Explicitly

An empty array, a missing field, and a null field are different API states. Save src/test/java/EdgeArraysTest.java. The empty() matcher declares that /empty returned a collection with zero elements. The /nullable response is a two-element array whose first element is JSON null and whose second element is the string ready.

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;

import org.junit.jupiter.api.Test;

class EdgeArraysTest {
  @Test
  void emptyAndExplicitNull() throws Exception {
    try (ArrayApi api = ArrayApi.start()) {
      given().baseUri(api.baseUri())
          .when().get("/empty")
          .then().statusCode(200)
          .body("
quot;, empty()); given().baseUri(api.baseUri()) .when().get("/nullable") .then().statusCode(200) .body("
quot;, hasSize(2), "[0]", nullValue(), "[1]", is("ready")); } } }

Do not use hasSize(0) against a path that may be absent unless the contract treats absence as an error elsewhere. A missing path generally evaluates to null rather than an empty list, but exact behavior can depend on the expression. Check the enclosing object and the relevant path explicitly when distinguishing omission from []. For an optional collection, write separate tests for the three meaningful responses: field omitted, field present with null, and field present with []. If your API contract normalizes absent data to an empty array, then a missing field is a regression even though both can look like "no results" to a caller. Likewise, hasItem(nullValue()) answers whether some element is null, while [0] checks its position. Verify: run mvn -q -Dtest=EdgeArraysTest test. Temporarily target /tags for the empty assertion to confirm that a nonempty root array fails.

Step 8: Extract When the Rule Spans Elements

Matchers are strongest when the response rule fits one selected path. A uniqueness constraint across IDs is easier to read after extraction. Save src/test/java/UniqueIdsTest.java. REST Assured's getList asks for integer IDs, and Hamcrest asserts both their sequence and distinct count. This test also demonstrates that extraction follows an HTTP status check, so an error page cannot quietly become a parsing result.

import static io.restassured.RestAssured.given;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.is;

import io.restassured.response.Response;
import java.util.HashSet;
import java.util.List;
import org.junit.jupiter.api.Test;

class UniqueIdsTest {
  @Test
  void idsAreUnique() throws Exception {
    try (ArrayApi api = ArrayApi.start()) {
      Response response = given().baseUri(api.baseUri())
          .when().get("/orders");
      response.then().statusCode(200);
      List<Integer> ids = response.jsonPath().getList("id", Integer.class);
      assertThat(ids, contains(101, 102, 103));
      assertThat(new HashSet<>(ids).size(), is(ids.size()));
    }
  }
}

contains is useful here for the fixture's known order, but the distinct-count assertion is the transferable rule. In a real service, also validate that ID values are present and have the expected type before relying on HashSet semantics. If the endpoint paginates, uniqueness on one page is weaker than uniqueness across all pages; combine this technique with API pagination testing. Verify: run mvn -q test. Surefire should discover all six test classes and exit successfully. Run mvn test without -q if you want the visible test count. Keep this command for a CI smoke job because the fixture needs no external service.

Troubleshooting

Problem: A path reports null although the JSON visibly contains the field -> Check whether the response root is an array or object. id projects IDs from the root array used here; orders.id would require an object with an orders key. Log the response once with .log().body() during diagnosis, then remove noisy logging from the normal green path. The REST Assured logging filters guide covers more controlled logging.

Problem: contains fails but the expected values are all present -> Inspect ordering and duplicate counts. contains requires the same sequence, while containsInAnyOrder ignores order but still requires the same number of each value. Use hasItems only when additional values are allowed by the contract.

Problem: A findAll expression produces an empty list -> Verify the field name, string case, and predicate syntax. GPath uses it.status == 'PAID' in the example. A Jayway JSONPath filter copied into body() is a different language and will not select the same path.

Problem: The response is HTML or plain text and array assertions fail -> Check the HTTP status and Content-Type first. Authentication redirects, gateway failures, and wrong URLs commonly return non-JSON bodies. The local fixture sets application/json; your service should do the same for JSON responses.

Problem: Compilation fails on org.hamcrest.Matchers or JUnit annotations -> Confirm the test classes are under src/test/java, dependencies use test scope, and Maven resolved the pinned artifacts. Run mvn -version to check its Java runtime, then mvn test for the full compiler error instead of the quiet command.

Problem: The test passes locally but fails against a shared environment -> Determine whether the API guarantees ordering and data stability. Seed records or assert invariant properties rather than a snapshot of changing data. Use a reusable REST Assured request and response specification for shared base URI, headers, and common response requirements.

Interview Questions and Answers

Q: When would you choose hasItems over containsInAnyOrder for a JSON array?

Choose hasItems when the contract requires selected members but permits additional members. Choose containsInAnyOrder when the complete array must match, including duplicate counts, but order is irrelevant. A passing hasItems check does not prove size or exclusivity.

Q: How do you assert an array's order in REST Assured?

Select the array or a field projection and pass it to Hamcrest contains. For example, body("id", contains(101, 102, 103)) checks the root array's IDs in sequence. Add that constraint only if the endpoint promises ordering.

Q: Can everyItem prove that a response contains records?

No. An all-elements condition can be satisfied by an empty collection. Add hasSize, not(empty()), or another nonempty check when presence matters.

Q: How does GPath differ from JSONPath here?

REST Assured's JSON path expressions use Groovy GPath, including find and findAll closures. Jayway JSONPath uses different filter syntax. Mixing the two often yields parse errors or null selections.

Q: How do you verify nested objects inside an array?

Select the parent item by index only when order is contractual; otherwise locate it by a stable identifier. Then address its child array, such as find { it.id == 101 }.items.sku, and apply a matcher appropriate to the child ordering rule.

Q: Why extract a list when Hamcrest already has matchers?

Extraction makes cross-element or cross-page invariants straightforward. Uniqueness, relationships among sibling records, and calculations over multiple fields can be clearer in Java. Keep direct body() checks for simple response contracts so failures point to the exact path.

For more REST Assured interview practice, see REST Assured interview questions and answers.

Common Mistakes

  • Using hasItems as if it enforced exact contents. Add hasSize or choose containsInAnyOrder when extras are defects.
  • Asserting contains against a response whose order is unspecified. That creates flaky tests when the backend changes query plans.
  • Filtering the response before checking a rule meant to apply to every element. The filter can hide precisely the bad record you need to catch.
  • Assuming duplicate values disappear under containsInAnyOrder. Hamcrest compares occurrences, so duplicate counts remain part of the assertion.
  • Treating an empty list as equivalent to a missing or null field. Define the API contract for each case and select the enclosing path when necessary.
  • Comparing numeric JSON values to a mismatched Java number type. For decimal or large integer fields, inspect the parser result and configure number handling deliberately before choosing equalTo.

Conclusion

REST Assured Hamcrest matchers for JSON arrays work best when each assertion reflects one precise API promise. Select the right array with GPath, then choose inclusion, exact order, exact unordered contents, size, or a per-element predicate. For rules that relate multiple entries, extract the list and write a focused Java assertion. Run the complete local suite once, then adapt its paths and expected values to your service contract.

Where To Go Next

Move the fixture-backed patterns into one endpoint test at a time. Begin with a stable response and document whether its array order, duplicates, empty behavior, and null handling are contractual. Add JSON schema validation for broad structure, request and response specs for shared setup, and API pagination tests when arrays span pages. If you are building a larger suite, the REST Assured API framework tutorial shows how to organize these assertions around reusable clients and test data.

Interview Questions and Answers

Which matcher would you use for an unordered array with no extra elements?

I would use `containsInAnyOrder`. It requires every expected element and preserves multiplicity, so an extra value or missing duplicate fails. I would first confirm that the endpoint does not promise a sort order.

Why can a test using hasItems pass despite a response defect?

`hasItems` verifies inclusion only. An API can return unexpected records or duplicate members and still satisfy it. If the contract defines the full array, I would use `containsInAnyOrder` or combine membership with cardinality checks.

How do you assert that all returned statuses are allowed?

I would project the status field and apply `everyItem(anyOf(is("PAID"), is("PENDING")))`. If an empty response is invalid, I would also assert the root array size or nonemptiness. That prevents a vacuous pass.

How would you test a nested items array without relying on order?

I would locate the parent object by a stable ID with GPath `find`, select its `items` field, and use `containsInAnyOrder` on a stable projection such as SKU. If duplicate SKUs are permitted, I would include the expected count of each occurrence. I would avoid positional selectors unless the API contract specifies order.

What is the role of GPath in REST Assured body assertions?

GPath selects or transforms the response value passed to the Hamcrest matcher. For instance, `findAll { it.status == 'PAID' }.id` filters objects and projects their IDs. The matcher then evaluates the resulting Java collection, rather than the raw JSON string.

How would you distinguish an empty array from a missing field?

I would assert the parent object contains the field and then assert that the selected value is an empty collection. A missing path may evaluate to null, which should not be treated as `[]` when the contract requires a present array. I would add separate negative tests for omission and explicit null if the service can produce both.

When should a test extract an array instead of asserting through body()?

I extract when the rule compares multiple entries, combines fields, or spans pages. A uniqueness check on IDs is straightforward after `getList("id", Integer.class)` and conversion to a set. I still validate status first and leave simple per-path checks in `body()` for readable failures.

Frequently Asked Questions

How do I assert a JSON array in REST Assured?

Select the array in `body()`, using ` REST Assured Hamcrest Matchers for JSON Arrays | QAJobFit for an anonymous root array or a field path for nested data. Pass a Hamcrest matcher such as `hasSize`, `contains`, or `containsInAnyOrder` based on the API contract."}},{"@type":"Question","name":"What is the difference between hasItems and containsInAnyOrder?","acceptedAnswer":{"@type":"Answer","text":"`hasItems` requires the named values to occur but allows extras. `containsInAnyOrder` requires the complete multiset of values, including duplicate counts, while ignoring their order."}},{"@type":"Question","name":"How can I verify array order?","acceptedAnswer":{"@type":"Answer","text":"Use Hamcrest `contains` against the whole array or a projected field list. It fails when the values, positions, or number of occurrences differ."}},{"@type":"Question","name":"How do I check every object in a JSON array?","acceptedAnswer":{"@type":"Answer","text":"Select the object array and use `everyItem` with an object matcher such as `hasKey`. Add `hasSize` or another presence check if an empty result should fail."}},{"@type":"Question","name":"How do I assert an empty JSON array?","acceptedAnswer":{"@type":"Answer","text":"Use `body(\"$\", empty())` for an empty root array, or select the nested array path and apply `empty()`. Assert the HTTP status and content type as well so an error response does not masquerade as the expected state."}},{"@type":"Question","name":"Does REST Assured use Jayway JSONPath filters?","acceptedAnswer":{"@type":"Answer","text":"No. REST Assured's JSON path syntax is Groovy GPath. Use expressions such as `findAll { it.status == 'PAID' }.id` for filtering and projecting array elements."}},{"@type":"Question","name":"How can I check duplicate values in an array?","acceptedAnswer":{"@type":"Answer","text":"Use `contains` when order matters or `containsInAnyOrder` when it does not. Include each expected occurrence in the matcher arguments; `hasItems` alone does not enforce duplicate counts."}}]}]} for an anonymous root array or a field path for nested data. Pass a Hamcrest matcher such as `hasSize`, `contains`, or `containsInAnyOrder` based on the API contract.

What is the difference between hasItems and containsInAnyOrder?

`hasItems` requires the named values to occur but allows extras. `containsInAnyOrder` requires the complete multiset of values, including duplicate counts, while ignoring their order.

How can I verify array order?

Use Hamcrest `contains` against the whole array or a projected field list. It fails when the values, positions, or number of occurrences differ.

How do I check every object in a JSON array?

Select the object array and use `everyItem` with an object matcher such as `hasKey`. Add `hasSize` or another presence check if an empty result should fail.

How do I assert an empty JSON array?

Use `body("

quot;, empty())` for an empty root array, or select the nested array path and apply `empty()`. Assert the HTTP status and content type as well so an error response does not masquerade as the expected state.

Does REST Assured use Jayway JSONPath filters?

No. REST Assured's JSON path syntax is Groovy GPath. Use expressions such as `findAll { it.status == 'PAID' }.id` for filtering and projecting array elements.

How can I check duplicate values in an array?

Use `contains` when order matters or `containsInAnyOrder` when it does not. Include each expected occurrence in the matcher arguments; `hasItems` alone does not enforce duplicate counts.

Related Guides