skip to content

Two custom REST Assured filters must run in a fixed order — how do you guarantee it?

level: seniorimportance: must knowfreq 46%

answer

  1. ordering is a number on the class
  2. lower value runs earlier
  3. plain filters all sit at one thousand
  4. equal orders are documented as arbitrary
  5. first on the request, last on the response

basics

~20 s

Make each filter implement OrderedFilter and return a getOrder value; REST Assured sorts the whole filter list by that number before sending, lowest first. A plain Filter counts as DEFAULT_PRECEDENCE, one thousand, so give the earlier filter a smaller order.

solid answer

~50 s

Implement `io.restassured.filter.OrderedFilter`, which extends `Filter` with one extra method, `int getOrder()`. REST Assured sorts the request's whole filter list by that value just before sending and runs it lowest first, so a **low** number means high precedence. Filters that stay on plain `Filter` are treated as `DEFAULT_PRECEDENCE`, which is `1000`; the constants `HIGHEST_PRECEDENCE` and `LOWEST_PRECEDENCE` are `Integer.MIN_VALUE` and `Integer.MAX_VALUE`. So a filter that must stamp `X-Keeper-Token` before a signing filter returns something like `HIGHEST_PRECEDENCE + 10` and the signer stays at the default. Two details bite in practice: the Javadoc says equal order values sort arbitrarily, so registration order is not a guarantee, and filters nest, so the filter that touches the request first is the one that sees the response last. Leave wide gaps between numbers so a third filter can be inserted later.

code

java · 25 lines
java
import io.restassured.filter.FilterContext;
import io.restassured.filter.OrderedFilter;
import io.restassured.response.Response;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification;

import static io.restassured.RestAssured.given;

given()
    .filter(new OrderedFilter() {
        public int getOrder() { return HIGHEST_PRECEDENCE + 10; }

        public Response filter(FilterableRequestSpecification requestSpec,
                               FilterableResponseSpecification responseSpec,
                               FilterContext ctx) {
            requestSpec.replaceHeader("X-Keeper-Token", currentKeeperToken());
            ctx.setValue("keeperTokenStamped", Boolean.TRUE);
            return ctx.next(requestSpec, responseSpec);
        }
    })
    .filter(requestSigningFilter)
.when()
    .post("/lighthouses/pigeon-point/maintenance-visits")
.then()
    .statusCode(201);

go deeper

for a junior

Know that ordering exists and that it comes from implementing OrderedFilter and returning a number from getOrder, with lower running earlier. You are unlikely to be asked to design the numbering yet.

for a middle

Explain the mechanics: the whole list is sorted before sending, plain filters sit at DEFAULT_PRECEDENCE of one thousand, and the constants are Integer.MIN_VALUE and Integer.MAX_VALUE. Say why equal values are unsafe to rely on.

for a senior

Show that you have debugged this. Talk about the inversion on the response path, about passing state through the context rather than a shared field, and about choosing spaced numbers so a later filter can be inserted without renumbering.

for a principal

Treat inter-filter ordering as coupling the suite has to own. Argue for few filters with single responsibilities and explicit precedence, because an ordering contract nobody wrote down is discovered only when a run fails in a way no test asserts.

## Precedence is a number, not a registration position A plain `Filter` gives REST Assured no ordering information at all. To control order you implement `io.restassured.filter.OrderedFilter`, which extends `Filter` and adds one method, `int getOrder()`. Just before the request goes out, REST Assured sorts the request's whole filter list by that number and runs it **lowest first** — a low value means high precedence, the same convention as a servlet `load-on-startup` value. Any filter that does not implement `OrderedFilter` is treated as `OrderedFilter.DEFAULT_PRECEDENCE`. - `OrderedFilter.DEFAULT_PRECEDENCE` is **1000** — the value every plain `Filter` is given. - `OrderedFilter.HIGHEST_PRECEDENCE` is `Integer.MIN_VALUE`, so it runs before everything. - `OrderedFilter.LOWEST_PRECEDENCE` is `Integer.MAX_VALUE`, so it runs after everything you wrote. - The interface Javadoc warns that **equal order values sort arbitrarily** — do not lean on the order in which you called `given().filter(...)`. - The sort covers the whole list, so where a filter was registered — globally or on this one request — does not decide when it runs; only its number does. So the fix for "my token filter must run before my signing filter" is to make the token filter an `OrderedFilter` returning, say, `HIGHEST_PRECEDENCE + 10`, and leave the signing filter at the default 1000. Give the two numbers a wide gap rather than 999 and 1000, so a third filter can be slotted between them later without renumbering the suite. ## Running first means seeing the response last Filters are around advice and they nest. The filter that runs first is the outermost one: it calls `ctx.next(...)`, which invokes the next filter, which invokes the next, until the internal sender performs the call. The response then unwinds back out. That inversion is the detail that catches people out on the maintenance API. | order value | when it touches the request | when it touches the response | |---|---|---| | `HIGHEST_PRECEDENCE + 10` | first | last | | `1000` (a plain `Filter`) | in the middle | in the middle | | `LOWEST_PRECEDENCE` | last, just before the send | first, straight off the wire | A filter meant to observe **exactly what was sent** to `POST /lighthouses/{lighthouseId}/maintenance-visits` therefore wants a *high* order number, because everything that rewrites the request has already run by then. A filter meant to see the response **before** anything rewrote it wants the same high number for the opposite reason. ## Handing a value to the filter behind you `FilterContext` carries one property map for the whole request, and every context handed to a later filter shares it. That is the supported way for two of your own filters to cooperate without a static field or a `ThreadLocal`: - `ctx.setValue("keeperTokenIssuedAt", System.currentTimeMillis())` in the early filter. - `ctx.getValue("keeperTokenIssuedAt")` in a later one, typed by the call site. - `ctx.hasValue(name)` and `ctx.hasValue(name, value)` when the later filter must behave differently if the earlier one did not run. Compare that with the alternative, a field on the filter instance: a field is shared by every request that uses that instance, so under a parallel suite it is a race. A context value lives for one request. ## `ctx.send(...)` is not `ctx.next(...)` `FilterContext` also exposes `Response send(RequestSender requestSender)`, and confusing it with `next(...)` produces a request that fires and a chain that never completes. They do different jobs: 1. `next(request, response)` continues the existing chain and eventually reaches the internal sender, which performs *the request under test*. Its result is what `then()` validates. 2. `send(requestSender)` issues an **additional** request at the same method and URI through the specification you hand it, and does **not** advance the chain. REST Assured's own form-auth filter uses it to fetch a login page mid-flight. 3. Calling `next(...)` a second time does not restart the chain either: the context consumes an iterator over the remaining filters, so the second call carries on from where the first stopped and returns `null` once the iterator is spent. REST Assured has no built-in retry, and a filter is not the place to invent one. ## What to argue for in review Ordering between filters is real coupling, and it deserves the same treatment as any other. Name the number in the filter class rather than in a comment, keep one job per filter so precedence stays easy to reason about, and make the instances stateless so a shared specification can register them once without the suite becoming order- and thread-dependent.

  • Two filters both sit at DEFAULT_PRECEDENCE — is the order you registered them in guaranteed?
    No. The `OrderedFilter` Javadoc states that equal order values produce arbitrary sort positions, so registration order is an implementation detail rather than a promise. If two filters genuinely depend on each other, give them distinct `getOrder()` values with a gap between them; if they do not, the ambiguity is harmless and worth leaving alone rather than pinning down.
  • Three ordered filters return 100, 500 and 900. Which one sees the response first?
    The one returning 900. Filters nest like around advice: 100 runs first and calls `ctx.next(...)`, which enters 500, then 900, then the internal sender. The response unwinds in reverse, so 900 sees it straight off the wire and 100 sees it last, after the other two have had their chance to rebuild it.
  • How should two of your own filters share a value for one request?
    Through the `FilterContext` property map: `ctx.setValue(name, value)` in the earlier filter and `ctx.getValue(name)` or `ctx.hasValue(name)` in the later one. The map is shared by every context in that request and nothing else, so it survives the chain without the thread-safety problem a field on a shared filter instance would create.

saying these in an interview costs you the question

  • Believes filters run in the order they were registered
  • Thinks a higher getOrder number means the filter runs earlier
  • Assumes a plain Filter always runs before an OrderedFilter
  • Expects the filter that runs first to see the response first
  • Shares state between filters through a field on a reused instance
  • Calls ctx.next twice expecting the chain to run again