In REST Assured, how do you assert that a response arrived inside a time budget?
answer
- milliseconds unless you say otherwise
- the matcher is typed on Long
- the unit conversion truncates
- client-observed round trip
- one sample, not a performance test
basics
~20 sUse then().time(Matcher) to match the round trip in milliseconds, as in time(lessThan(2000L)). The matcher is typed Matcher<Long>, so long literals are required. Add a TimeUnit for another unit, remembering the conversion truncates. REST Assured records the figure on every call.
solid answer
~50 sWith `then().time(...)`. `time(Matcher<Long>)` matches the round trip in **milliseconds** — `time(lessThan(2000L))` — and `time(Matcher<Long>, TimeUnit)` converts first, as in `time(lessThan(2L), SECONDS)`. No setup is needed; REST Assured records the round trip for every request and the assertion reads that figure. Two things catch people. The parameter is typed `Matcher<Long>`, so `lessThan(2000)` will not compile and you need the `L`. And the explicit-unit conversion truncates, so a 1 999 ms response is *1* second and *0* days — the failure message prints both figures. The number is the client-observed round trip, including connection setup and reading the response, not the service's own processing time, and one sample beside a functional check is a smoke test, not a performance measurement. Set the bound generously and on few endpoints, or it becomes the flakiest line in the suite.
code
java · 18 linesimport static io.restassured.RestAssured.given;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.hamcrest.Matchers.*;
given()
.baseUri("https://api.paddleport.example/v1")
.queryParam("launchSite", "harbour-quay")
.when()
.get("/kayaks")
.then()
.statusCode(200)
// milliseconds by default; note the long literals
.time(allOf(greaterThan(0L), lessThan(2000L)));
// explicit unit: converted first, and the conversion truncates
given().baseUri("https://api.paddleport.example/v1")
.when().get("/kayaks")
.then().time(lessThan(2L), SECONDS);go deeper
Be able to add a response-time expectation to a then() chain and to remember that the default unit is milliseconds and the literal needs an L.
Explain what the figure measures, why the explicit-unit form truncates, and why that makes a seconds budget looser than it looks.
Judge where a time expectation belongs at all, keep the bounds noise-tolerant, and recognise it as a smoke check rather than evidence about latency.
Decide whether functional suites should carry latency expectations at all, and where real latency evidence comes from instead.
## The assertion `ValidatableResponseOptions` declares two response-time checks: - `time(Matcher<Long> matcher)` — matches the round trip **in milliseconds**. - `time(Matcher<Long> matcher, TimeUnit timeUnit)` — converts the measurement into `timeUnit` first, then matches. So a budget on a kayak-availability call reads: ```java when().get("/kayaks").then().statusCode(200).time(lessThan(2000L)); ``` No setup is required. REST Assured records the round trip for every request it sends, and `time(...)` reads that recorded figure. Both forms are ordinary expectations on the validate side: a miss throws `AssertionError` like any other failed check, and the chain is the same one that carries `statusCode` and `header`. ## `Matcher<Long>` means long literals The parameter is typed `Matcher<Long>`, not `Matcher<? extends Number>`. That has a very practical consequence in Java: - `time(lessThan(2000L))` compiles; `time(lessThan(2000))` does not, because `lessThan(2000)` is a `Matcher<Integer>`. - The same applies inside composites: `time(allOf(greaterThan(0L), lessThan(2000L)))`. - With an explicit unit the literal is in that unit: `time(lessThan(2L), SECONDS)`. It is the most common way this assertion fails to compile, and the fix is a single `L`. ## The unit conversion truncates The explicit-unit form converts the recorded milliseconds into the requested unit with an integer conversion, and integer conversion **truncates toward zero**. That is easy to under-estimate: | measured | asserted unit | value the matcher sees | |---|---|---| | 1 999 ms | `MILLISECONDS` | 1999 | | 1 999 ms | `SECONDS` | 1 | | 1 999 ms | `DAYS` | 0 | | 1 999 ms | `NANOSECONDS` | 1 999 000 000 | - A coarse unit makes the assertion far weaker than it reads: `time(lessThan(2L), SECONDS)` accepts anything up to 2 999 ms. - A very fine unit makes it far stricter: a nanosecond budget of `3` can never pass, because a millisecond-resolution measurement converts to a multiple of a million nanoseconds. - The failure message prints both figures — `was 1999 milliseconds (1 seconds)` — which is usually what tells you the conversion is the problem. - Prefer the default millisecond form for budgets in the hundreds or low thousands; it is the unit the number was actually measured in. - The truncation is silent: nothing warns you that a seconds budget just became a 50% wider one, so the choice of unit is part of the assertion's meaning rather than a formatting detail. ## What the number is, and is not - It is the **client-observed round trip**: the time REST Assured spent performing the request and consuming the response, measured around the call. - It therefore includes connection setup, network transit and the client's own reading of the response, not just the service's processing. - It does not come from the server, and no header on the response is consulted. - If no measurement is available the assertion fails with `No time was recorded, cannot perform response time validation.` rather than silently passing. - Because the measurement is wall-clock based, a cold JVM inflates the first calls; REST Assured's own timing tests warn about exactly that and are skipped where clock resolution is poor. ## Where it sits in the chain `time(...)` is an expectation like any other on the validate side, which has two practical consequences worth stating. 1. It composes with the rest of the chain — `statusCode(200).contentType(ContentType.JSON).time( lessThan(2000L))` is one call with three claims, and the timing claim costs nothing extra because the measurement was taken anyway. 2. It is a *validation*, not a cut-off. Nothing is cancelled when the budget is exceeded: the request runs to completion, the response is consumed, and only then does the expectation fail. A slow endpoint therefore still costs the suite its full wall-clock time. ## Using it honestly One sample from one call is a smoke check, not a performance measurement — what a latency figure means statistically, and how a load profile is shaped to produce one, is a different subject entirely. Written well, `time(...)` catches the coarse regression that a body assertion cannot see: an endpoint that suddenly answers in eight seconds because a new join went unindexed. Written badly, it is the flakiest line in the suite. - Set the bound generously — an order of magnitude above the normal figure, not a tight one. - Put it on a small number of endpoints whose latency you actually care about, not on every call. - Expect noise from shared CI machines, cold connection pools and the first request of a run. - Never treat a green `time(...)` as evidence about production latency; it is one client-side sample taken beside a functional assertion.
- Why does then().time(lessThan(2000)) fail to compile?The overload is `time(Matcher<Long>)`, and `lessThan(2000)` is a `Matcher<Integer>` because the literal is an `int`. Write `lessThan(2000L)`. The same applies inside composites such as `allOf(greaterThan(0L), lessThan(2000L))`, and to the explicit-unit form, where the literal is expressed in the unit you passed.
- A response takes 1 999 ms. Why does time(lessThan(2L), SECONDS) pass?The measurement is converted into the requested unit with an integer conversion that truncates toward zero, so 1 999 ms becomes 1 second and the matcher sees 1. A coarse unit silently loosens the budget — that call would accept anything below 3 000 ms. Assert in milliseconds when the bound is in the hundreds or low thousands.
- Does then().time(...) measure the server's processing time?No. It is the client-observed round trip that REST Assured recorded around the call, so it includes connection setup, network transit and REST Assured's own consumption of the response. Nothing is read from the response itself. Treat it as a coarse regression guard beside a functional assertion, not as a latency measurement of the service.
saying these in an interview costs you the question
- Passing an int literal to a matcher typed on Long
- Reading a coarse TimeUnit budget as if it rounded up
- Calling one timed request a performance test
- Assuming the figure is the server's processing time
- Setting a tight bound and then re-running until CI is green