given().baseUri(...) and given().basePath(...) set the two pieces that sit in front of the path you hand to a verb. On an allotment water-rota API, given().baseUri("https://rota.allotment-water.test").basePath("/api/v2").get("/plots/17/slots") targets https://rota.allotment-water.test/api/v2/plots/17/slots. REST Assured concatenates the base URI (including any path the base URI itself carries), then the base path, then the verb's path, normalising the seams: a missing slash is inserted and a doubled one collapsed. Both calls are per-request — they overwrite the values the specification copied from the static RestAssured.baseURI and RestAssured.basePath at given(), and nothing outside that chain sees the change. There is no merge and no precedence arithmetic: the last write to the specification's field is what the verb reads. With neither set, the base URI is RestAssured.DEFAULT_URI, the literal http://localhost, and the base path is empty. Reach for the per-request form when one call must leave the suite's usual target — a warden-portal health probe, say.
REST Assured never holds a finished URL for a request. It holds three separate pieces and joins them at the moment a verb is called, so the address a test actually reaches is always the product of an assembly rule rather than a stored string. Knowing that rule is the difference between a suite you can repoint at another environment in one line and one that quietly hits the wrong host.
The three pieces
- Base URI — the scheme and authority, optionally carrying a path prefix of its own; set it with
given().baseUri("https://rota.allotment-water.test").
- Base path — a path fragment shared by a family of calls; set it with
given().basePath("/api/v2").
- The verb path — the string handed to
get(...), post(...), delete(...) or request(...), for example "/plots/17/slots".
Written against an allotment water-rota API, given().baseUri("https://rota.allotment-water.test").basePath("/api/v2").when().get("/plots/17/slots") sends a GET to https://rota.allotment-water.test/api/v2/plots/17/slots.
How the join works
REST Assured concatenates the pieces left to right and normalises every seam, so you never have to count slashes:
- Start with the base URI's scheme and authority.
- Append the path the base URI itself carries, if any —
https://rota.allotment-water.test/gateway contributes /gateway.
- Append the base path.
- Append the verb path.
At each seam a single slash is inserted when neither side supplies one, and a doubled slash is collapsed back to one. That is why baseUri("http://localhost/") with get("/plots") and baseUri("http://localhost") with get("plots") resolve to the same address. It also means a base URI that already carries a prefix and a base path stack rather than compete: /gateway plus /api/v2 plus /plots yields /gateway/api/v2/plots.
Per-request call versus the static field
Both names exist twice — as a field on io.restassured.RestAssured and as a method on RequestSpecification. The relationship is copy-then-overwrite:
| RestAssured.baseURI / RestAssured.basePath | given().baseUri(...) / given().basePath(...) |
|---|
| Scope | every request built afterwards | only the chain you called it on |
| When it is read | copied into the specification at given() | written onto that specification directly |
| Effect on other tests | changes all of them | none |
| Typical use | the suite's normal target | one call that must go somewhere else |
Because the statics are copied into the specification when the chain is created, a per-request call simply overwrites the copy. There is no merge and no "most specific wins" arithmetic: the last write to the specification's field is what the verb reads. So given().baseUri("https://warden.allotment-water.test").get("/plots") in one test leaves every other test pointed wherever the suite normally points.
That also means the two forms are not in competition and never need to be reconciled. A suite normally sets the target once, in whatever hook runs before the first test, and then writes plain relative paths everywhere; the per-request methods are for the handful of calls that must go somewhere else. Reading a chain top to bottom therefore tells you the whole answer: if it names a base URI, that is the target, and if it does not, the target is whatever the run was configured with.
What you get when you set nothing
- The base URI defaults to
RestAssured.DEFAULT_URI, the literal string http://localhost.
- The base path defaults to
RestAssured.DEFAULT_PATH, the empty string.
- A bare
get("/plots") in a fresh JVM therefore targets http://localhost:8080/plots; the 8080 comes from a separate port rule that fires for a localhost authority.
- Passing
null to either method throws a NullPointerException naming "Base URI" or "Base Path" — there is no argument that unsets them again.
That default is shaped for a service booted on the same machine as the test run. The moment a suite targets anything else, one of the two has to be set explicitly.
Putting each piece in the right place
- Put the scheme, host and port in the base URI: that is the part that differs between a laptop and a deployed environment.
- Put the version or gateway prefix in the base path.
/api/v2 is a property of the API rather than of the environment, so it belongs where a target switch will not disturb it.
- Put the resource path in the verb, so a reader scanning the test sees
/plots/17/slots at the call site instead of buried in setup.
- Keep that verb path relative. A path beginning with a scheme discards both defaults outright, and that is the most common reason a repointed suite still reaches the old host.
The payoff is an address assembled from parts you can each change independently, every one of them visible in the chain that sends the request.