REST Assured 7.0.0
REST Assured 7.0.0 no longer includes a path engine or Groovy, and sends the requests with the JDK’s own HTTP client instead of Apache HttpClient 4. GPath, the path syntax REST Assured has always used, is a dependency that you add yourself, and so is Apache HttpClient 5 if you want it instead of the JDK client. 7.0.0 also adds a second path engine, JSONPath with Jayway, and clients, so that tests that run in parallel can each have their own settings.
7.0.0-RC1 is the release candidate for 7.0.0. Please try it, and report what doesn’t work in the issue tracker.
What’s new in 7.0
Section titled “What’s new in 7.0”- Path engines.
rest-assuredno longer includes a path engine, and so no longer brings Groovy. Addjson-path-gpath, andxml-path-gpathfor XML and HTML, and your paths keep working. They are the new names ofjson-pathandxml-path. See Upgrading to 7.0 and Path engines. - JSONPath. A second JSON path engine,
json-path-jayway, evaluates JSONPath with Jayway JsonPath, such as$.store.book[?(@.price < 10)].title. See JSONPath with Jayway. - The HTTP client. The JDK’s
java.net.http.HttpClientsends the requests, from the newhttp-client-jdkmodule, whichrest-assureddepends on. Apache HttpClient 4, and with it httpmime, httpcore, commons-codec and commons-logging, is no longer a dependency of any REST Assured artifact (#1862, #1696). Addhttp-client-apache5to send the requests with Apache HttpClient 5 instead, for example for NTLM or theHostheader, which the JDK client can’t send.httpClientConfig().client(..)picks the client andcustomizeClient(..)customizes it. See The HTTP client. - Clients for parallel tests.
RestAssured.useClient(..)binds aRestAssuredClientto the thread of a test, so tests that run in parallel can each have their own settings and keep using plaingiven()(#534).given(client)names a client for one request.RestAssuredMockMvcClientandRestAssuredWebTestClientClientdo the same for Spring, and Kotlin getsGiven(client) { }and ScalaGiven(client)(..). See Clients for parallel tests. - Specifications. Sending a request, or validating a response, no longer changes the specification, so one specification can be used from many threads (#945). Specifications get the static settings, or those of the client, when they’re used, instead of copying the static settings when they’re built (#1977). See Specifications no longer change when sent or validated and Where requests get their settings.
- Jackson 3.
json-schema-validatorvalidates with the networknt json-schema-validator 3.0.9, which brings Jackson 3, and the java-json-tools validator is optional.JsonPathnow prefers Jackson 3, like the object mapping, soresponse.jsonPath().getObject(..)andresponse.as(..)use the same Jackson. See JSON Schema validation and Jackson 3. - OAuth 1 no longer needs scribejava: REST Assured signs the requests itself. See OAuth 1 without scribejava.
- Removed APIs. The APIs that take or return HttpClient 4 types, deprecated in 6.3.0,
HTTPBuilder, the root path methods deprecated since 4.0.0, such asroot(..), and Jackson 1 support are removed. The OpenRewrite recipeio.restassured.UpgradeToRestAssured7changes most of this in your Java code. See Removed APIs and Let OpenRewrite do it. - Logging. The logging filters take their settings from the config of the request when they log, and every response log now hides the blacklisted headers, not only
then().log(). See Logging. - Dependencies. Hamcrest 3.0, Groovy 4.0 to 6.x for the GPath modules, Jayway JsonPath 3.0.0 for
json-path-jayway, and a Java 17 execution environment in OSGi. See Other changes.
Upgrading to 7.0
Section titled “Upgrading to 7.0”Start from REST Assured 6.3.0 without deprecation warnings, as Preparing for REST Assured 7.0 describes. Then change the version to 7.0.0-RC1 and, if your tests use paths, add a path engine. Or let OpenRewrite do it. Which module do I need lists every module, what it’s for and how to keep their versions in line.
Add a path engine
Section titled “Add a path engine”REST Assured 7.0.0 no longer brings json-path and xml-path, and so no longer brings Groovy either. If your tests use paths, such as body("lotto.lottoId", equalTo(5)), path(..), extract().path(..) or jsonPath(), add GPath to your test dependencies. Your paths keep working unchanged:
<dependency> <groupId>io.rest-assured</groupId> <artifactId>json-path-gpath</artifactId> <version>7.0.0-RC1</version> <scope>test</scope></dependency>Gradle:
testImplementation("io.rest-assured:json-path-gpath:7.0.0-RC1")Tests that use paths on XML or HTML, or call xmlPath() or htmlPath(), also need xml-path-gpath:
<dependency> <groupId>io.rest-assured</groupId> <artifactId>xml-path-gpath</artifactId> <version>7.0.0-RC1</version> <scope>test</scope></dependency>Gradle:
testImplementation("io.rest-assured:xml-path-gpath:7.0.0-RC1")Without a path engine, the first path fails with a message that names the dependency to add:
java.lang.IllegalStateException: REST Assured can't evaluate the path "lotto.lottoId" because no JSON path engine is on the classpath.jsonPath(), xmlPath() and htmlPath() fail the same way: “REST Assured can’t run jsonPath() because no JSON path engine is on the classpath.” See Path engines for what needs which engine. For JSON, the message also offers JSONPath with Jayway, which suits new tests written in JSONPath rather than the paths you have.
If you use rest-assured-all, you don’t need to do anything: it still contains json-path-gpath and xml-path-gpath and depends on Groovy.
The new artifact names
Section titled “The new artifact names”json-path and xml-path are renamed json-path-gpath and xml-path-gpath, with the same packages and classes. If you depend on json-path or xml-path directly, switch to the new names. In 7.x the old names are relocation poms that resolve to the new ones at the same version, so a build that still uses them keeps working: Maven prints a warning, and Gradle resolves them silently. The old names are removed in REST Assured 8.0.0.
Spring Boot 3
Section titled “Spring Boot 3”Spring Boot 3’s dependency management pins rest-assured, rest-assured-common, json-path and xml-path to REST Assured 5.5.x. Set the rest-assured.version property, so that all of them change together. Spring Boot 3.5 imports rest-assured-bom at that version, so the new modules, such as json-path-gpath and http-client-apache5, get it too:
<properties> <rest-assured.version>7.0.0-RC1</rest-assured.version></properties>With Gradle and the Spring dependency management plugin: ext["rest-assured.version"] = "7.0.0-RC1" in build.gradle.kts. Without Spring Boot, import io.rest-assured:rest-assured-bom instead to keep the REST Assured artifacts on the same version, see Keep the versions in line.
If you only change the version of rest-assured, Spring Boot 3 keeps json-path 5.5.x on the classpath. Its classes are in the same packages as those of json-path-gpath, so REST Assured stops with a message that tells you to replace it with json-path-gpath or to set rest-assured.version, instead of failing later with a NoSuchMethodError. Likewise, REST Assured now checks that rest-assured-common, json-path-gpath and xml-path-gpath have the same major and minor version as rest-assured, and fails with a message that names both versions. A different patch version only logs a warning.
Spring Boot 4
Section titled “Spring Boot 4”Spring Boot 4 doesn’t manage REST Assured, so it neither pins its version nor reads a rest-assured.version property. Set the version on each REST Assured dependency, or import rest-assured-bom and leave the versions out:
<dependencyManagement> <dependencies> <dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured-bom</artifactId> <version>7.0.0-RC1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies></dependencyManagement>Gradle:
testImplementation(platform("io.rest-assured:rest-assured-bom:7.0.0-RC1"))Spring Boot 4 manages some libraries that REST Assured’s modules use, and the versions it picks work: Groovy 5.0.x for json-path-gpath and xml-path-gpath, Jayway JsonPath 2.10.0 for json-path-jayway, Apache HttpClient 5.5.x (Spring Boot 4.0) or 5.6.x (Spring Boot 4.1) for http-client-apache5, and Kotlin 2.2 or later for the Kotlin extensions. It manages both Jackson 2 and Jackson 3. With both on the test classpath, REST Assured uses Jackson 3, see JSON Schema validation and Jackson 3.
Let OpenRewrite do it
Section titled “Let OpenRewrite do it”The rest-assured-openrewrite module contains the OpenRewrite recipe io.restassured.UpgradeToRestAssured7. It:
- runs
io.restassured.MigrateRestAssured6Deprecations, which moves your Java code from the APIs deprecated in 6.3.0 (see Preparing for REST Assured 7.0), - removes the SSL port settings, renames the root path methods deprecated since REST Assured 4.0.0, such as
root(..)torootPath(..), and replacesContextAuthenticationSchemewithAuthenticationScheme(see Removed APIs), - changes
json-pathandxml-pathtojson-path-gpathandxml-path-gpathin Maven and Gradle builds, - upgrades the
io.rest-assureddependencies to 7.0.0-RC1, including a version property such asrest-assured.version, - and adds
json-path-gpathwhere your Java code evaluates paths, such asbody("a.b", ..),path(..),rootPath(..)orjsonPath(), andxml-path-gpathwhere it callsxmlPath()orhtmlPath()or usesXmlPath. They get the scope (or Gradle configuration) and version of your REST Assured dependency, often${rest-assured.version}.
A path on an XML body looks just like a path on a JSON body, so the recipe can’t tell that a test needs xml-path-gpath for it. Add xml-path-gpath yourself if your tests use paths on XML or HTML. The recipe changes Java code only, so change Kotlin, Groovy and Scala code yourself.
With Maven, run this in your project:
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \ -Drewrite.recipeArtifactCoordinates=io.rest-assured:rest-assured-openrewrite:7.0.0-RC1 \ -Drewrite.activeRecipes=io.restassured.UpgradeToRestAssured7With Gradle, add the OpenRewrite plugin and the recipe to build.gradle.kts:
plugins { id("org.openrewrite.rewrite") version "latest.release"}
rewrite { activeRecipe("io.restassured.UpgradeToRestAssured7")}
dependencies { rewrite("io.rest-assured:rest-assured-openrewrite:7.0.0-RC1")}Then run gradle rewriteRun. Review the changes with git diff before you commit them.
Code that no longer compiles
Section titled “Code that no longer compiles”jsonConfig().numberReturnType(JsonPathConfig.NumberReturnType.BIG_DECIMAL).JsonConfigno longer knowsjson-path’s enum. Useio.restassured.common.config.NumberReturnTypeinstead.- Storing the result of an
ObjectMapperConfigfactory getter, such asjackson2ObjectMapperFactory(), or ofJsonConfig.numberReturnType(), in a variable of ajson-pathorxml-pathtype. They now return therest-assured-commontypes inio.restassured.common.mapper.factoryandio.restassured.common.config. The factories you set still work, since the old factory types extend the new ones. - Jackson 1.
ObjectMapperType.JACKSON_1,JsonParserType.JACKSON_1,Jackson1ObjectMapperFactory,jackson1ObjectMapperFactory(..)andJsonPath.using(Jackson1ObjectMapperFactory)are removed, and Jackson 1 on the classpath is no longer used for object mapping. Use Jackson 2 or 3. - Code that calls
jsonPath(),xmlPath()orhtmlPath(), or usesJsonPathorXmlPath, needsjson-path-gpathorxml-path-gpathto compile. - Code that uses the APIs that take or return HttpClient 4 types, or
HTTPBuilder. See Removed APIs. - Code that uses HttpClient 4 classes itself, which REST Assured no longer brings. Add
org.apache.httpcomponents:httpclientto your own build if you need them. - The root path methods deprecated since REST Assured 4.0.0, such as
root(..),appendRoot(..)andJsonPath.setRoot(..). UserootPath(..),appendRootPath(..)andsetRootPath(..), see Removed APIs.
The OpenRewrite recipe changes the first two, the root path methods, and the SSL and multipart methods among the removed APIs.
Mocks of Response
Section titled “Mocks of Response”Response still has jsonPath(), xmlPath() and htmlPath(), which return JsonPath and XmlPath. Code that never calls them runs without a path engine. But reflection over all the methods of Response, which is what Mockito’s mock(Response.class) and Spock’s Mock(Response) do, loads JsonPath and XmlPath. If you mock Response, keep json-path-gpath and xml-path-gpath on the test classpath.
Path engines
Section titled “Path engines”REST Assured 7.0.0 has no path engine by default. There are two:
- GPath, the path syntax REST Assured has always used, such as
lotto.winners.winnerId. It comes in two modules:json-path-gpathfor JSON, andxml-path-gpathfor XML and HTML. They depend on Groovy, which REST Assured itself no longer needs. - JSONPath, such as
$.lotto.winners[*].winnerId, with Jayway JsonPath, injson-path-jayway. JSON only, and without Groovy.
Pick GPath if you’re upgrading from 6.x: your paths keep working unchanged.
| You use | You need |
|---|---|
A path on a JSON body: body("a.b", ..), path(..), extract().path(..), rootPath(..) |
json-path-gpath |
| A path on an XML or HTML body | xml-path-gpath |
jsonPath() or JsonPath |
json-path-gpath |
xmlPath(), htmlPath() or XmlPath |
xml-path-gpath |
Anything else, such as statusCode(..), headers, cookies, body(containsString(..)), hasXPath(..), JSON schema validation or as(Greeting.class) |
Nothing |
The same goes for RestAssuredMockMvc, RestAssuredWebTestClient and the Kotlin and Scala extensions.
Groovy versions
Section titled “Groovy versions”json-path-gpath, xml-path-gpath and rest-assured-all are built with Groovy 5.1.3 and depend on it. They work with Groovy 4.0, 5.x and 6.x, which CI tests (4.0.33, 5.0.8 and 6.0.0), so the Groovy version that Spring Boot manages works as it is: 4.0.x in Spring Boot 3.5, and 5.0.x in Spring Boot 4. They’re built with Groovy 5 because code compiled by Groovy 6 doesn’t run on Groovy 4 or 5. In OSGi, the bundles import Groovy from 4.0 up to, but not including, 7.0.
With Groovy 6, two things differ:
- A JSON body that is just a string with an invalid escape, such as
"\x", fails to parse, with Groovy’sgroovy.json.JsonExceptionas the cause, instead of givingnull. The same escape inside an object or an array fails with every Groovy version. - An XML body nested more than 1000 elements deep fails, unless the system property
jdk.xml.maxElementDepthis set.
JSONPath with Jayway
Section titled “JSONPath with Jayway”json-path-jayway evaluates JSON paths in body(..), path(..), extract().path(..), rootPath(..), equalToPath(..) and ResponseAwareMatchers with Jayway JsonPath:
<dependency> <groupId>io.rest-assured</groupId> <artifactId>json-path-jayway</artifactId> <version>7.0.0-RC1</version> <scope>test</scope></dependency>Gradle:
testImplementation("io.rest-assured:json-path-jayway:7.0.0-RC1")get("/lotto").then().body("$.lotto.winners[*].winnerId", hasItems(23, 54));get("/store").then().body("$.store.book[?(@.price < 10)].title", hasItems("Sayings of the Century", "Moby Dick"));A path may leave out the leading $., so lotto.lottoId works too. jsonPath() and JsonPath are GPath. With Jayway, use JaywayPath.from(response.asString()), which returns Jayway’s DocumentContext configured like the module:
List<Integer> winnerIds = JaywayPath.from(get("/lotto").asString()).read("$.lotto.winners[*].winnerId");The module needs Jayway JsonPath 2.8.0 or later, and brings 3.0.0, which needs Java 17. A version that your build manages, such as Spring Boot 4’s 2.10.0, keeps working. If dependency management sets a version older than 2.8.0, the module fails with a message that says so. Jayway logs through SLF4J, so without an SLF4J provider on the classpath you see SLF4J’s three-line “No SLF4J providers were found” warning once. Add a provider such as slf4j-simple or logback-classic in test scope to replace it with real logging.
Picking the engine
Section titled “Picking the engine”With only one of json-path-gpath and json-path-jayway on the classpath, REST Assured uses that one. With both, it uses GPath and logs once, at INFO level through the io.restassured System.Logger, that it does. To use Jayway, set JsonConfig.pathEngine(..):
// For all testsRestAssured.config = RestAssured.config().jsonConfig(jsonConfig().pathEngine(JsonPathEngine.JAYWAY));
// For one requestgiven().config(config().jsonConfig(jsonConfig().pathEngine(JsonPathEngine.JAYWAY))). ..
// For a request or response specificationRequestSpecification requestSpec = new RequestSpecBuilder().setConfig(config().jsonConfig(jsonConfig().pathEngine(JsonPathEngine.JAYWAY))).build();ResponseSpecification responseSpec = new ResponseSpecBuilder().setConfig(config().jsonConfig(jsonConfig().pathEngine(JsonPathEngine.JAYWAY))) .expectBody("$.lotto.winners[*].winnerId", hasItems(23, 54)).build();A response specification’s config outranks the request’s, which outranks RestAssured.config. The configs are merged per type, so the JsonConfig of a response specification replaces the request’s whole JsonConfig, including a numberReturnType(..) set globally. That lets one test class move to Jayway while the others keep GPath. pathEngine(..) with an engine whose module isn’t on the classpath fails with “JsonConfig.pathEngine is JAYWAY but io.rest-assured:json-path-jayway isn’t on the classpath.”
The same goes for RestAssuredMockMvc and RestAssuredWebTestClient, with their own config.
How Jayway differs from GPath
Section titled “How Jayway differs from GPath”- Lists. GPath’s
lotto.winners.winnerIdcollectswinnerIdfrom each element of thewinnerslist. In JSONPath that’slotto.winners[*].winnerIdor$..winnerId. With Jayway,lotto.winners.winnerIdfails with “JSONPath (Jayway) can’t evaluate lotto.winners.winnerId: lotto.winners is a list, so use lotto.winners[*].winnerId.” When GPath isn’t on the classpath, the message also suggests addingjson-path-gpath, in case the path comes from a 6.x test. - Missing values. A missing property gives
nullin both engines, sobody("missing", nullValue())works with both. A filter or wildcard that matches nothing gives an empty list in Jayway. - GPath syntax. Closures such as
findAll { it.price < 10 }, GPath methods such asunique()orcollect { }, and a quoted key after a dot, such asstore.'my-key', aren’t JSONPath and fail as an invalid path. Use a filter, such as[?(@.price < 10)], and bracket notation, such asstore['my-key']. Jayway haslength(), and acceptssize(). It applies a function after[*], such assum(), to each element, so aggregate over a deep scan instead, such as$..price.sum(). - Keys that start with
$. A leading$schemais the key$schema, as in GPath. Use$['$schema']to make it explicit. - Numbers follow
JsonConfig.numberReturnType(..)as with GPath:FLOAT_AND_DOUBLEby default, andBIG_DECIMALkeeps the scale, so12.50stays12.50. - Invalid JSON. Jayway’s parser is strict. GPath reads some invalid JSON that Jayway fails on: a trailing comma (
[1,]), a leading zero ([01]), a raw control character in a string, and anything after the document ({"a":1}garbage).
The REST Assured bundles require a Java 17 execution environment (osgi.ee JavaSE 17), which every Java 17 framework provides.
The rest-assured bundle resolves without the path engine bundles, Groovy and SPI Fly. Install the json-path-gpath and xml-path-gpath bundles (and Groovy 4.0 to 6.x), or the json-path-jayway bundle, to use paths. They’re found even if they’re installed after rest-assured.
json-path-jayway needs the Jayway JsonPath bundle (json-path) and what it requires: json-smart (net.minidev.json-smart), accessors-smart (net.minidev.accessors-smart), ASM (org.objectweb.asm) and slf4j-api (slf4j.api). With Jayway JsonPath 2.9.0 and later, that’s slf4j-api 2, which in turn requires an SLF4J provider bundle, such as slf4j-simple (slf4j.simple), and an OSGi ServiceLoader Mediator, such as Apache Aries SPI Fly (org.apache.aries.spifly.dynamic.bundle). Jayway JsonPath 2.8.0 needs slf4j-api 1.7 and an SLF4J 1.7 binding bundle, such as slf4j-simple 1.7, but no SPI Fly. Jayway JsonPath 3.0.0, which json-path-jayway brings, also requires the Jackson 3 bundles tools.jackson.core.jackson-core and tools.jackson.core.jackson-databind, and com.fasterxml.jackson.core.jackson-annotations 2.x, which jackson-databind 3 imports.
The HTTP client
Section titled “The HTTP client”REST Assured sends the requests with an HTTP client module. There are two:
http-client-jdkuses the JDK’sjava.net.http.HttpClientand needs nothing else.rest-assureddepends on it, so you don’t have to add anything.http-client-apache5uses Apache HttpClient 5. It behaves more like Apache HttpClient 4, which REST Assured 6 used, and does some things that the JDK client can’t. You add it yourself.
Apache HttpClient 5
Section titled “Apache HttpClient 5”To send the requests with Apache HttpClient 5, add http-client-apache5 to your test dependencies:
<dependency> <groupId>io.rest-assured</groupId> <artifactId>http-client-apache5</artifactId> <version>7.0.0-RC1</version> <scope>test</scope></dependency>Gradle:
testImplementation("io.rest-assured:http-client-apache5:7.0.0-RC1")REST Assured then uses it instead of the JDK client, unless you pick another client (see Picking the client). rest-assured-bom manages its version. Like the other REST Assured modules, it must have the same major and minor version as rest-assured, or the request fails with a message that names both versions.
Like Apache HttpClient 4 in REST Assured 6, it creates a client, and so a new connection, for each request, gives the response headers in the order and case the server sent them, with the server’s reason phrase, and sends several Cookie headers as they are. It sends the Host, Connection, Upgrade and Expect headers of a request, answers NTLM challenges (auth().ntlm(..)), connects to a proxy with the scheme https over TLS, with the SSL settings of the request, and logs in to a proxy with Basic for an HTTPS request. The JDK client can’t do those. See How Apache HttpClient 5 differs from the JDK client.
REST Assured follows the redirects, answers the Basic and Digest challenges, keeps the cookies and decodes gzip and deflate itself, so HttpClient 5 does none of that, and it retries nothing. Its timeouts are infinite unless httpClientConfig() sets them. Its read timeout covers each read of the body too, and its connect timeout also covers the TLS handshake of a new connection.
The module supports Apache HttpClient 5.2.1 and later, with HttpCore 5.2 and later, and depends on 5.6.5. If dependency management, such as a BOM, sets an older version, or HttpClient 5 is excluded, the request fails with a message that says what’s missing or too old, for example:
java.lang.IllegalStateException: http-client-apache5 needs Apache HttpClient 5.2.1 or later, but 5.1.3 is on the classpath, probably set by a BOM such as Spring Boot's.REST Assured doesn’t use the JDK client instead, because you added http-client-apache5 on purpose. HttpClient 5 brings httpcore5-h2, which it uses for HTTP/1.1 too, so don’t exclude it. Without it, the request fails with a message that says so.
Apache HttpClient 5 logs through SLF4J, which prints a warning when there’s no SLF4J backend. To get rid of it, add a backend that matches the slf4j-api version on your test classpath, such as slf4j-simple or slf4j-nop, in test scope. HttpClient 5 brings slf4j-api 1.7.36, unless your build manages another version.
If the application under test runs in the same JVM as the tests, for example with @SpringBootTest, adding http-client-apache5 also puts Apache HttpClient 5 on its classpath. Spring’s RestClient and RestTemplate then pick HttpClient 5 too, and HttpClient 5 retries a 503 or 429 response with a Retry-After header once by default. REST Assured’s own requests are never retried, and REST Assured leaves the application’s client alone. To keep the application on the client it had, set its request factory, for example to JdkClientHttpRequestFactory, or set the Spring Boot property that picks it (spring.http.client.factory=jdk in recent versions).
Picking the client
Section titled “Picking the client”Without a choice, REST Assured uses http-client-apache5 if it’s on the classpath, and otherwise http-client-jdk. In full, REST Assured uses the first of these:
- The client that
httpClientConfig().client(..)picks, in the config of the request. - The client that the system property
restassured.http.clientnames. http-client-apache5, if it’s on the classpath.http-client-jdk.
It logs the client it uses once through the io.restassured System.Logger: at DEBUG level for http-client-jdk when nothing chose it, and otherwise at INFO level, for example “REST Assured uses the HTTP client from io.rest-assured:http-client-apache5 (Apache HttpClient 5.6.5), found on the classpath. To pin a client, use httpClientConfig().client(..) or the system property restassured.http.client.”
Pick a client for all requests, or for one:
RestAssured.config = RestAssured.config().httpClient(httpClientConfig().client(HttpClientType.JDK));given().config(RestAssured.config().httpClient(httpClientConfig().client(HttpClientType.APACHE5))). ..Or with a system property, for every request that doesn’t pick one in code: -Drestassured.http.client=jdk or -Drestassured.http.client=apache5. Another library can provide a client by implementing io.restassured.common.spi.http.HttpClientProvider. REST Assured only uses such a client when you name it, with client("name"), client(ProviderClass.class) or the system property.
A client that you pick but that isn’t on the classpath fails the request with a message that says which dependency to add. If you exclude http-client-jdk and have no other client, the request fails with “REST Assured found no HTTP client module. Add io.rest-assured:http-client-jdk (no other dependencies) or io.rest-assured:http-client-apache5 to your test dependencies.”
Customizing the client
Section titled “Customizing the client”The settings of httpClientConfig(), such as connectTimeout(..), readTimeout(..) and expectContinue(..), work with every client (see HTTP Client Config). For anything else, customize the builder of the client’s library:
RestAssured.config = RestAssured.config().httpClient(httpClientConfig() .customizeClient(java.net.http.HttpClient.Builder.class, builder -> builder.executor(executor)));The customizers run in the order you add them. A customizer for the builder of another client, such as Apache HttpClient 5’s HttpClientBuilder while the JDK client is used, fails the request with a message, for example “A customizer for org.apache.hc.client5.http.impl.classic.HttpClientBuilder is configured, but REST Assured uses the JDK HTTP client.”
The JDK client is shared by all requests with the same settings, so they share its connections too. connectionConfig().closeIdleConnectionsAfterEachResponse() gives each request a new client instead.
For Apache HttpClient 5, customize its org.apache.hc.client5.http.impl.classic.HttpClientBuilder. The customizers get the builder after REST Assured has set it up:
RestAssured.config = RestAssured.config().httpClient(httpClientConfig() .customizeClient(HttpClientBuilder.class, builder -> builder.addRequestInterceptorFirst( (request, entity, context) -> request.addHeader("X-Trace-Id", traceId))));REST Assured can’t read what a customizer set on the builder, so this is what each setting does:
| A customizer that calls | Has this effect |
|---|---|
The interceptors, such as addRequestInterceptorFirst(..), and setUserAgent(..), setDefaultHeaders(..), setKeepAliveStrategy(..), setConnectionReuseStrategy(..) and evictIdleConnections(..) |
They apply |
setConnectionManager(..) |
Replaces REST Assured’s connection manager, together with its SSL settings, its connect timeout and its pool, which has no limit. It’s the way to limit the connections, and connectionRequestTimeout(..) then limits the wait for one. closeIdleConnectionsAfterEachResponseAfter(..) doesn’t reach it |
setDefaultAuthSchemeRegistry(..) |
Replaces REST Assured’s registry. Keep "ntlm" (NTLMSchemeFactory.INSTANCE) in it for auth().ntlm(..), and "basic" and "digest" for the login to a proxy |
useSystemProperties() |
HttpClient reads the system properties http.agent, for the User-Agent, and http.keepAlive, for whether connections are kept, and nothing else |
Retries, redirects, cookies, decompression, setTargetAuthenticationStrategy(..) and setRoutePlanner(..) |
No effect. REST Assured switches them back after the customizers, since it does them or decides them itself |
setDefaultRequestConfig(..), setDefaultCredentialsProvider(..) and setProxy(..) |
No effect, since each request has REST Assured’s config, credentials and route. Use httpClientConfig(), auth() and proxy(..) instead |
A client shuts its connection manager down when it’s closed, and REST Assured closes the client of a request after its response, unless reuseHttpClientInstance() keeps the client. So create the connection manager inside the customizer, or create it once, call setConnectionManagerShared(true) and close it yourself. A limit that holds across requests needs reuseHttpClientInstance() or such a shared connection manager:
RestAssured.config = RestAssured.config().httpClient(httpClientConfig() .reuseHttpClientInstance() .customizeClient(HttpClientBuilder.class, builder -> builder.setConnectionManager( PoolingHttpClientConnectionManagerBuilder.create().setMaxConnTotal(10).build())));A request whose connection manager has been shut down fails with an IllegalStateException that says so.
How the JDK client differs from HttpClient 4
Section titled “How the JDK client differs from HttpClient 4”| What | HttpClient 4 | JDK client |
|---|---|---|
Response header names, in headers(), the logs and assertion messages |
As the server sent them, in its order | In lower case and sorted by name. Assertions on headers ignore the case of the name, so they pass |
| Status line | The server’s reason phrase | The standard reason phrase of the status code, such as HTTP/1.1 404 Not Found, whatever the server sent |
User-Agent |
Apache-HttpClient/4.5.14 |
Java-http-client/<Java version>. Set the header for another |
| Connections | A new connection for each request | Requests with the same settings share one client, and reuse its connections. A response whose body is never read keeps its connection until it’s garbage collected. The javax.net.ssl trust store properties are read when that client is created |
Read timeout (readTimeout(..)) |
Each pause while reading | Connecting and waiting for the response headers, not reading the body. It’s a ConnectionTimeoutException when it fires before the connection is up |
connectionRequestTimeout(..) |
The wait for a pooled connection | No effect, since the JDK client doesn’t limit its connections. A warning is logged once |
closeIdleConnectionsAfterEachResponseAfter(..) with an idle time above 0 |
Closes the connections that were idle for longer after each response | No effect, since the JDK client closes idle connections after a time that it decides. A warning is logged once. See jdk.httpclient.keepalive.timeout under JVM properties |
| Retries | Its retry handler | The JDK client retries when connecting fails, or when a reused connection turns out to be closed. See JVM properties |
| Closing a partly read body stream | Reads the rest of the body | Closes the connection |
Several Cookie headers |
Sent as they are | Joined into one |
| A proxy’s Basic login request (407) | Answered once, then the login is sent up front, also after a redirect or a challenge | Answered for each request that REST Assured sends, also after a redirect or a challenge. A Proxy-Authorization header is only sent to the proxy |
| A proxy that refuses an HTTPS tunnel | Its response | An IOException |
OPTIONS and TRACE without a body, and HEAD on Java 17 |
No Content-Length |
Content-Length: 0 |
Expect: 100-continue and a chunked body |
As written here | Expect: 100-Continue and Transfer-encoding: chunked |
A host name that a HostnameVerifier rejects |
SSLPeerUnverifiedException |
SSLException caused by the SSLPeerUnverifiedException |
| A body cut short | ConnectionClosedException |
A plain IOException |
| HTTP version | HTTP/1.1 | HTTP/1.1 |
JDK client limitations
Section titled “JDK client limitations”The JDK client can’t:
- send the
Host,ConnectionorUpgradeheaders, unless the JVM runs with-Djdk.httpclient.allowRestrictedHeadersnaming them, - send an
Expectheader other than100-continue, - do NTLM authentication (
auth().ntlm(..)), - use TLS to the proxy (
ProxySpecification.withScheme("https")), - log in to a proxy with Basic for an HTTPS request, unless the JVM runs with
-Djdk.http.auth.tunneling.disabledSchemes=(an empty value), - send with an
Authenticatorset throughcustomizeClient(..).
Those fail the request with an UnsupportedOperationException whose message says what to do, for example “The JDK HTTP client (the default module, http-client-jdk) can’t send a Host header. Add io.rest-assured:http-client-apache5 to your test dependencies and REST Assured uses it, or start the JVM with -Djdk.httpclient.allowRestrictedHeaders=host.” Apache HttpClient 5 can do all of them but the last.
How Apache HttpClient 5 differs from the JDK client
Section titled “How Apache HttpClient 5 differs from the JDK client”| What | JDK client | Apache HttpClient 5 |
|---|---|---|
Response header names, in headers(), the logs and assertion messages |
In lower case and sorted by name | As the server sent them, in its order, as with HttpClient 4 |
| Status line | The standard reason phrase of the status code | The server’s reason phrase, as with HttpClient 4 |
User-Agent |
Java-http-client/<Java version> |
Apache-HttpClient/<version> (Java/<Java version>). HttpClient 4 sent Apache-HttpClient/4.5.14 |
| Connections | Requests with the same settings share one client, and reuse its connections | A new client, and so a new connection, for each request, as with HttpClient 4. The client lasts across the redirects of the request and the answers to its login challenges, and is closed after the response. With reuseHttpClientInstance(), the requests share one client, which keeps its connections, with no limit on their number |
Read timeout (readTimeout(..)) |
Connecting and waiting for the response headers, not reading the body | Each read, also of the body after the request, as with HttpClient 4 |
connectionRequestTimeout(..) |
No effect. A warning is logged once | Only has an effect when a customizer sets a connection manager that limits the connections |
closeIdleConnectionsAfterEachResponseAfter(..) with an idle time above 0 |
No effect. A warning is logged once | Closes the connections that were idle for longer after each response, as with HttpClient 4, for a client kept with reuseHttpClientInstance(). Without it, the client of each request is closed after its response anyway |
| Retries | When connecting fails, or when a reused connection turns out to be closed | None |
| Closing a partly read body stream | Closes the connection | Drops the connection, so a client kept with reuseHttpClientInstance() sends the next request on a new connection, as it does after a response whose body is never read. HttpClient 4 read the rest of the body first |
Several Cookie headers |
Joined into one | Sent as they are, as with HttpClient 4 |
| A proxy’s Basic login request (407) | Answered for each request that REST Assured sends, also after a redirect or a challenge | Answered once, then the login is sent with every later request of the client, as with HttpClient 4 |
| A proxy that refuses an HTTPS tunnel | An IOException |
Its response with HttpClient 5.4 and later, as with HttpClient 4. A ClientProtocolException with HttpClient 5.2 and 5.3 |
OPTIONS and TRACE without a body, and HEAD on Java 17 |
Content-Length: 0 |
No Content-Length, as with HttpClient 4 |
A custom method without a body, such as PURGE |
Content-Length: 0, as with HttpClient 4 |
No Content-Length |
A host name that a HostnameVerifier rejects |
SSLException caused by the SSLPeerUnverifiedException |
SSLPeerUnverifiedException, as with HttpClient 4 |
| A server that rejects a missing client certificate, with TLS 1.3 | SSLException |
SSLException, or a SocketException (broken pipe), depending on timing |
| HTTP version | HTTP/1.1 | HTTP/1.1 |
Apache HttpClient 5 also does what the JDK client can’t, apart from the Authenticator: it sends the Host, Connection and Upgrade headers and any Expect header as given, answers NTLM challenges, connects to a proxy with the scheme https over TLS, with the SSL settings of the request, and logs in to a proxy with Basic for an HTTPS request. NTLM is deprecated in HttpClient 5 and may be removed in HttpClient 6.
What Apache HttpClient 5 can’t do
Section titled “What Apache HttpClient 5 can’t do”Apache HttpClient 5 can’t:
- send a body with a
TRACErequest. It fails with anUnsupportedOperationExceptionwhose message says what to do, for example “Apache HttpClient 5 (found on the classpath, http-client-apache5) can’t send a body with a TRACE request. Choose another client with httpClientConfig().client(..).” - send a
TRACErequest with anAuthorizationorCookieheader, with HttpClient 5.4 and later, - send an
OPTIONSrequest with a body but without aContent-Type, with HttpClient 5.4 and later (HttpCore 5.3 and later).
The last two fail with HttpClient’s ClientProtocolException, whose cause names the rule, such as “TRACE request MUST NOT contain an Authorization header.” or “OPTIONS request must have Content-Type header”. The JDK client, and HttpClient 4 in REST Assured 6, send all three.
Like the JDK client, it sends HTTP/1.1 only.
Content-Length and Transfer-Encoding headers
Section titled “Content-Length and Transfer-Encoding headers”REST Assured sends the length of the body itself, as a Content-Length header, or chunked when the length is unknown. A request that sets a Content-Length or Transfer-Encoding header, for example with header("Content-Length", ..), fails with an UnsupportedOperationException, whichever client sends it:
java.lang.UnsupportedOperationException: REST Assured can't send a Content-Length header that the request sets. Remove the header: REST Assured sends the length of the body itself, as a Content-Length header, or chunked when the length is unknown.Remove the header. No client can send these headers, so changing the client doesn’t help. In REST Assured 6, Apache HttpClient 4 refused them on a request with a body, with a ClientProtocolException (“Content-Length header already present”), but sent them as given on a GET, HEAD, DELETE, OPTIONS or TRACE request without a body. So, for example, given().header("Content-Length", "0").delete(..) worked in 6.x and now fails.
JVM properties
Section titled “JVM properties”The JDK reads these system properties for the whole JVM, most of them once, so set them where the JVM that runs the tests starts, for example in the argLine of the Maven Surefire plugin or jvmArgs of a Gradle Test task:
| Property | Effect |
|---|---|
jdk.httpclient.allowRestrictedHeaders=host,connection,upgrade |
Lets requests set the headers it names |
jdk.http.auth.tunneling.disabledSchemes= (an empty value) |
Allows a Basic proxy login for HTTPS requests, which the JDK turns off by default |
jdk.httpclient.disableRetryConnect=true |
Doesn’t retry when connecting fails |
jdk.httpclient.enableAllMethodRetry=true |
Also retries requests that aren’t idempotent, such as POST, on a reused connection that turns out to be closed |
jdk.httpclient.keepalive.timeout=<seconds> |
How long an idle connection is kept |
For example with Maven:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <argLine>-Djdk.httpclient.allowRestrictedHeaders=host</argLine> </configuration></plugin>Exceptions
Section titled “Exceptions”- A connect timeout throws
io.restassured.common.exception.ConnectionTimeoutException, and a read timeoutio.restassured.common.exception.ResponseTimeoutException. With Apache HttpClient 5, a timeout while waiting for a pooled connection throws aConnectionTimeoutExceptiontoo, and the read timeout also covers reading the body after the request, such as inasString(). The JDK client supports neither (see the table above). Both exceptions areSocketTimeoutExceptions. Before, HttpClient 4 threw itsConnectTimeoutExceptionorConnectionPoolTimeoutException, which aren’t, or a plainSocketTimeoutException. - A host that can’t be resolved still throws
java.net.UnknownHostException. With the JDK client its cause is the client’sConnectException. With Apache HttpClient 5 it has no cause and the same message as in 6.x. - A refused connection throws a
java.net.ConnectException: the JDK client’s own, or Apache HttpClient 5’sorg.apache.hc.client5.http.HttpHostConnectException. - Other errors of Apache HttpClient 5 are its own
IOExceptions, such asorg.apache.hc.client5.http.ClientProtocolException(see What Apache HttpClient 5 can’t do). - More redirects than
redirectConfig().maxRedirects(..)allows throwio.restassured.common.exception.TooManyRedirectsException, and a circular redirect, a relativeLocationrejected byrejectRelativeRedirect(true), an invalidLocationand a redirect without aLocationthrowjava.net.ProtocolException, with the same messages as before. Before, they were HttpClient 4’sClientProtocolExceptions.TooManyRedirectsExceptionis aProtocolException. io.restassured.internal.http.HttpResponseExceptionandResponseParseExceptionextendjava.io.IOExceptioninstead of HttpClient 4’sHttpResponseException.HttpResponseException.getResponse()and the constructors that took anHttpResponseDecoratorare removed.
Code that catches IOException keeps working.
Removed APIs
Section titled “Removed APIs”These APIs were deprecated in 6.3.0 (see Preparing for REST Assured 7.0) and are removed:
| Removed | Use instead |
|---|---|
SSLConfig.sslSocketFactory(..), getSSLSocketFactory(), x509HostnameVerifier(..) and getX509HostnameVerifier() |
sslContext(..), getSSLContext(), hostnameVerifier(..) and getHostnameVerifier() |
CertificateAuthSettings.sslSocketFactory(..), x509HostnameVerifier(..) and their getters |
sslContext(..), hostnameVerifier(..) and their getters |
CertAuthScheme.setSslSocketFactory(..), setX509HostnameVerifier(..) and their getters |
setSslContext(..), setHostnameVerifier(..) and their getters |
HttpClientConfig.httpMultipartMode(..) and httpMultipartMode() |
multipartMode(..) and multipartMode() |
HttpClientConfig.HttpClientFactory, httpClientFactory(..), httpClientInstance() and usesDefaultHttpClientFactory() |
The settings of httpClientConfig(), or customizeClient(..). redirectConfig().followRedirectsForAllMethods(true) instead of LaxRedirectStrategy, and keepResponseCookies(true) instead of the client’s cookie store |
QueryableRequestSpecification.getHttpClient() |
Nothing |
CookieFilter.getCookieStore() and extractEffectiveUriFromContext(..) |
getCookies() and addCookie(..) |
authenticate(HTTPBuilder) of the authentication schemes, and io.restassured.internal.http.HTTPBuilder |
authenticate(AuthenticationContext) |
SSLConfig.port(..) and CertificateAuthSettings.port(..), with their getPort(), CertAuthScheme.getPort() and setPort(..), and the port argument of AuthenticationContext.certificate(..) are removed too, without a deprecation first, because they never had an effect: an https URL without a port always went to port 443. Set the port in the URI, such as https://localhost:8443, or with port(..). The OpenRewrite recipe removes the calls.
The root path methods deprecated since REST Assured 4.0.0 are removed too:
| Removed | Use instead |
|---|---|
root(..) |
rootPath(..) |
noRoot() |
noRootPath() |
appendRoot(..) |
appendRootPath(..) |
detachRoot(..) |
detachRootPath(..) |
JsonPath.setRoot(..) and XmlPath.setRoot(..) |
setRootPath(..) |
The first four are methods of then(), expect() and ResponseSpecification, and of the validatable responses of RestAssuredMockMvc and RestAssuredWebTestClient. expect().appendRoot(path) detached the path instead of appending it, so code that used it now gets what appendRootPath(path) does. The OpenRewrite recipe renames the calls.
AuthenticationScheme now has the single method authenticate(AuthenticationContext), so a lambda can be one:
RestAssured.authentication = context -> context.header("X-Api-Key", apiKey);ContextAuthenticationScheme adds nothing to it any more and is deprecated. It’s kept until REST Assured 8.0.0, so that code written for 6.3.0 still compiles, and the OpenRewrite recipe replaces it with AuthenticationScheme. A class that extends one of REST Assured’s schemes and only overrides authenticate(HTTPBuilder) must override authenticate(AuthenticationContext) instead. A scheme compiled against REST Assured 6 that doesn’t implement authenticate(AuthenticationContext), for example from a shared test jar, fails the request with an IllegalStateException that names its class.
The OpenRewrite recipe changes the SSL and multipart methods and the root path methods, replaces ContextAuthenticationScheme, and marks the authentication schemes to change.
HttpClient 4 parameters
Section titled “HttpClient 4 parameters”The HttpClient 4 parameters set with httpClientConfig().setParam(..) and the other deprecated parameter methods still work for the keys that have a replacement, listed in Preparing for REST Assured 7.0, until 8.0.0. Any other key fails the request with an IllegalArgumentException that lists the settings to use instead, since REST Assured would otherwise drop it silently. The four redirect parameters, such as http.protocol.handle-redirects, have never had an effect, since the RedirectConfig outranks them, and log a warning once.
OSGi and the HTTP client
Section titled “OSGi and the HTTP client”The rest-assured bundle requires the http-client-jdk bundle (io.rest-assured:http-client-jdk), and no longer needs the HttpClient 4 bundles (httpclient, httpmime and httpcore). It imports http-client-apache5 as optional, also dynamically, so that bundle is found when it’s installed. The clients of other libraries are found with ServiceLoader, which in OSGi needs a ServiceLoader Mediator, such as Apache Aries SPI Fly.
http-client-apache5 is a bundle. HttpClient 5’s jars aren’t, so install them wrapped, for example with Pax URL’s wrap: or with bnd: httpclient5, httpcore5 and httpcore5-h2. They import each other and SLF4J, and what HttpClient 5 uses only when it’s there, such as Conscrypt and Brotli, should be imported as optional. Also install slf4j-api and an SLF4J provider, such as slf4j-simple, and with SLF4J 2 an OSGi ServiceLoader Mediator, such as SPI Fly. Without them, the http-client-apache5 bundle doesn’t resolve, and REST Assured uses the JDK client.
Clients for parallel tests
Section titled “Clients for parallel tests”The static settings of RestAssured are shared by all threads. Tests that run in parallel and need different settings, such as a port per Spring Boot test class, can now bind a client to their thread, and keep plain given() in the test bodies:
@BeforeEachvoid useOwnPort() { RestAssured.useClient(RestAssuredClient.builder().port(port).build());}
@AfterEachvoid unbindClient() { RestAssured.resetClient();}
@Testvoid shows_open_orders() { given().queryParam("status", "open").when().get("/orders").then().statusCode(200);}RestAssured.reset() also unbinds the client, but it resets the static settings for every thread, so in a parallel suite use resetClient(). given(client) names a client for one request, for example for a second service. A client named in given(client) wins over the one bound to the thread, which wins over the static settings. The binding isn’t inherited by other threads, so a condition that Awaitility polls on its own thread uses the static settings unless it names the client with given(client) or Awaitility polls with pollInSameThread().
A client is an immutable set of settings, built with RestAssuredClient.builder(), whose methods are named like the static fields and setters without set, or from the current static settings with RestAssured.client(). toBuilder() gives a builder for a changed copy. A client owns no connections or cookies and has no close(): its requests get their HTTP client by the same rules as those of the static API.
Without a client, everything works as before. See Parallel tests and RestAssuredClient in the usage guide, and Parallel tests with RestAssuredMockMvc for Spring.
Specifications no longer change when sent or validated
Section titled “Specifications no longer change when sent or validated”Sending a request, and validating a response with then().spec(..) or validate(..), now works on copies of the request and response specification, so the specifications you hold never change. One specification can be sent, or validate responses, many times and from many threads, and sends the same request each time (#945). Before, a specification sent twice sent the query parameters of the path twice, and one used from many threads mixed up the requests. The filter instances on a specification are still shared by every send, so a filter used from many threads must be thread safe.
Since the specification doesn’t change, you can no longer read back from it what the send set. SpecificationQuerier.query(spec) shows what was set before the send: no method, path or path parameters, no query parameters from the path’s query string, and no default headers, HTTP client, config or anything a filter added, such as a cookie from CookieFilter or the session id from SessionFilter. Filters and the failure listeners of FailureConfig get the copies, with everything set during the send. given(requestSpecification, responseSpecification) no longer links the two specifications, so afterwards, as for any built specification, requestSpecification.get(..) and responseSpecification.when() throw an IllegalStateException.
The config is fixed along the way:
given(requestSpecification, responseSpecification)validates with the config of the request specification, thenRestAssured.config, instead ofRestAssured.configas it was when the response specification was built. A config set onRestAssured.requestSpecificationapplies toexpect()assertions too.then().spec(responseSpecification)no longer leaves the config of the response in the specification, so a latergiven(requestSpecification, responseSpecification)doesn’t validate with it, and a failed validation logs its own request and response, not those of another thread.
Where requests get their settings
Section titled “Where requests get their settings”In 7.0.0, a request gets the static settings, or those of its client, where it’s used, and not where a specification was built. This section shows what changes, each with an example.
Spec builders no longer copy the static settings
Section titled “Spec builders no longer copy the static settings”RequestSpecBuilder and ResponseSpecBuilder no longer copy the static settings of RestAssured when they’re created, such as baseURI, port, basePath, authentication, config, filters(..), proxy, requestSpecification, rootPath, the registered parsers and defaultParser. The static settings, or those of a client, are applied when the specification is used, with given(..), given().spec(..), expect().spec(..) or then().spec(..), as they are at that time, and what was set on the builder is applied on top (#1977).
RestAssured.requestSpecification = new RequestSpecBuilder().addHeader("X-Tenant", "a").build();RequestSpecification spec = new RequestSpecBuilder().addQueryParam("q", "x").build();
given(spec).get("/search");// Before: X-Tenant was sent twice, once from the copy in spec// Now: X-Tenant is sent onceA change of RestAssured.config, filters(..), rootPath or the registered parsers after the builder was created now reaches the request and its validation, and a specification used with a client gets the settings of the client. SpecificationQuerier.query(spec) shows only what was set on the builder:
RestAssured.baseURI = "http://example.com";
SpecificationQuerier.query(new RequestSpecBuilder().build()).getBaseUri();// Before: http://example.com// Now: http://localhostTo keep a static setting with a specification, set it on the builder, for example new RequestSpecBuilder().setConfig(RestAssured.config()) or new ResponseSpecBuilder().rootPath(RestAssured.rootPath).
What a builder computes while building
Section titled “What a builder computes while building”Some builder methods compute something right away, and they now use the config of the builder, the default one unless setConfig(..) sets another, instead of the static config: the body that setBody(..) serializes, the session id name of setSessionId(value), the update strategy of addParam(..), addQueryParam(..) and addFormParam(..), the default control name and file name of addMultiPart(..), and which headers addHeader(..) overwrites. They aren’t redone when the specification is used.
RestAssured.config = RestAssured.config().sessionConfig(sessionConfig().sessionIdName("phpsessionid"));
new RequestSpecBuilder().setSessionId("1234").build();// Before: sends the cookie phpsessionid=1234// Now: sends JSESSIONID=1234
new RequestSpecBuilder().setConfig(RestAssured.config()).setSessionId("1234").build();// Now: sends phpsessionid=1234log(..) on a builder is the exception: it takes its settings from the config of the request when it logs, see Logging.
The Spring specification builders
Section titled “The Spring specification builders”MockMvcRequestSpecBuilder and WebTestClientRequestSpecBuilder no longer copy the static settings of RestAssuredMockMvc and RestAssuredWebTestClient either, such as the MockMvc or WebTestClient instance, basePath, config, requestSpecification, responseSpecification, authentication, resultHandlers(..) and postProcessors(..). The same rules as for RequestSpecBuilder apply, with setConfig(RestAssuredMockMvc.config()) or setConfig(RestAssuredWebTestClient.config()) to compute with the static config.
RestAssuredMockMvc.mockMvc(mockMvc);RestAssuredMockMvc.resultHandlers(print());MockMvcRequestSpecification spec = new MockMvcRequestSpecBuilder().addQueryParam("name", "Johan").build();
given().spec(spec).get("/greeting");// Before: print() ran twice// Now: print() runs once
spec.get("/greeting");// Before: used the static MockMvc instance// Now: fails with "You haven't configured a MockMVC instance"A specification sent on its own needs its own instance: new MockMvcRequestSpecBuilder().setMockMvc(mockMvc), or setStandaloneSetup(..) or setWebAppContextSetup(..), and setWebTestClient(..) for WebTestClient. A MockMvc or WebTestClient instance, or a base path, set statically after the builder was created now reaches the request.
given(requestSpecification, responseSpecification)
Section titled “given(requestSpecification, responseSpecification)”given(requestSpecification, responseSpecification) now merges both specifications into the static settings, or those of the client bound with RestAssured.useClient(..), like given().spec(requestSpecification).expect().spec(responseSpecification), instead of sending them as they were built. The static settings are read when given(..) is called, and RestAssured.responseSpecification now applies too.
RequestSpecification request = new RequestSpecBuilder().addQueryParam("q", "x").build();ResponseSpecification response = new ResponseSpecBuilder().expectContentType(ContentType.JSON).build();RestAssured.port = 8081;RestAssured.responseSpecification = new ResponseSpecBuilder().expectStatusCode(200).build();
given(request, response).get("/search");// Before: sent to port 8080, the port when the builder was created, and the status code wasn't checked// Now: sent to port 8081, and the status code must be 200Merging a response specification
Section titled “Merging a response specification”Merging a response specification, with expect().spec(..), then().spec(..), ResponseSpecBuilder.addResponseSpecification(..), given(requestSpecification, responseSpecification) or RestAssured.responseSpecification, now keeps the status code, status line, content type, response time, root path, default parser and log detail that the merged specification doesn’t set. Before, they were cleared.
ResponseSpecification json = new ResponseSpecBuilder().expectContentType(ContentType.JSON).build();
expect().statusCode(200).spec(json).when().get("/orders");// Before: the status code wasn't checked, since json expects none// Now: the status code must be 200Body expectations that the merged specification defined without a root path now take the root path in effect where they’re merged, such as RestAssured.rootPath or one set earlier in the chain:
ResponseSpecification common = new ResponseSpecBuilder().expectBody("status", equalTo("ok")).build();
get("/orders/42").then().rootPath("data").spec(common);// Before: checked status// Now: checks data.statusTo check status as before, merge common before you set the root path: then().spec(common).rootPath("data").
Equal headers and cookies are added once
Section titled “Equal headers and cookies are added once”Merging specifications, for example with given().spec(..), given(requestSpecification, responseSpecification) or RequestSpecBuilder.addRequestSpecification(..), no longer adds a header or cookie that’s already there with the same value, or the same multipart part, again. In the Spring modules, that includes the same result handler or post processor.
RequestSpecification a = new RequestSpecBuilder().addHeader("X", "1").build();RequestSpecification b = new RequestSpecBuilder().addHeader("X", "1").build();
given().spec(a).spec(b).get("/orders");// Before: X: 1 was sent twice// Now: X: 1 is sent onceSo the static headers, cookies and multipart parts are also sent once when the merged specification was made with given() or with(), as in given(with().header("X", "1"), expect().statusCode(200)). A header with the same name and another value is still added, and a header that HeaderConfig overwrites, such as Content-Type or Accept, still replaces the current one.
The page with the CSRF token
Section titled “The page with the CSRF token”A relative path of the page with the CSRF token, set with given().csrf(..) or CsrfConfig.csrfTokenPath(..), is now sent to the base URI, port and base path of the request, also with form authentication, instead of to RestAssured.baseURI, port and basePath.
given().baseUri("http://localhost:8081").csrf("/users/form").formParam("firstName", "John").when().post("/users");// Before: fetched the token from http://localhost:8080/users/form// Now: fetches it from http://localhost:8081/users/formFor a request sent to a full URL, such as post("http://localhost:8081/users"), give the page with the CSRF token as a full URL too. The request for that page now also uses the config and the proxy of the request, so relaxedHTTPSValidation() or a proxy set on the request applies to it, and the extra requests of form authentication and CSRF use the client of the request.
certificate(certURL, password)
Section titled “certificate(certURL, password)”RestAssured.certificate(certURL, password) now takes the SSL settings (the key store and trust store instances and types, the key store password, the host name verifier, the SSLContext and relaxed HTTPS validation) from the SSLConfig of the request when the request is sent: that of given().config(..) if it sets one, else that of the client of the request, else RestAssured.config.
RestAssured.authentication = certificate("/client.p12", "secret");RestAssured.config = RestAssured.config().sslConfig(sslConfig().keystoreType("PKCS12"));
get("https://localhost:8443/secured");// Before: certificate(..) took the SSL settings of RestAssured.config when it was called, so the key store type was ignored// Now: the key store type is usedThe scheme is still a CertAuthScheme, but getKeystoreType(), getTrustStoreType(), getKeyStore(), getTrustStore(), getHostnameVerifier() and getSslContext() no longer show the settings that are used, and its setters don’t change what is sent. given().auth().certificate(certURL, password) and the certificate(..) methods that take a CertificateAuthSettings are unchanged.
Logging
Section titled “Logging”The logging filter of log(), as in given().log().headers(), and of RequestSpecBuilder.log(..), MockMvcRequestSpecBuilder.log(..) and WebTestClientRequestSpecBuilder.log(..), now takes the blacklisted headers, the print stream, the URL encoding of the request URI and pretty printing from the config of the request when it logs:
given(). log().headers(). config(RestAssured.config().logConfig(logConfig().blacklistHeader("Authorization"))). header("Authorization", "Bearer secret").when(). get("/orders");// Before: printed Authorization=Bearer secret, since log() read the config as it was when log() was called// Now: prints Authorization=[ BLACKLISTED ]Pretty printing given explicitly, as in log().body(false), still wins.
RequestLoggingFilter, ResponseLoggingFilter and ErrorLoggingFilter created without a pretty-print argument now pretty print the body if the log config of the request has pretty printing enabled when they log:
RestAssured.config = RestAssured.config().logConfig(logConfig().enablePrettyPrinting(false));
given().filter(new RequestLoggingFilter()).body("{\"a\":1}").post("/orders");// Before: RequestLoggingFilter always pretty printed the body// Now: prints {"a":1} as it isBefore, ResponseLoggingFilter and ErrorLoggingFilter took the setting of RestAssured.config when they were created. expect().log().ifError(), ifStatusCodeIsEqualTo(..) and ifStatusCodeMatches(..) follow the log config of the request too.
The response log now hides the headers blacklisted with LogConfig.blacklistHeader(..) or blacklistDefaultSensitiveHeaders() too. Before, only then().log() hid them, so expect().log(), ResponseSpecBuilder.log(..) and logging if validation fails printed their values, for example to the log of a CI build. These, the response logs of form authentication and CSRF, and a ResponseLoggingFilter or ErrorLoggingFilter created without blacklisted headers now take them from the log config of the request when they log:
RestAssured.config = RestAssured.config().logConfig(logConfig().blacklistHeader("X-Session-Token"));RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
get("/login").then().statusCode(200);// Before: when the status code wasn't 200, the response log printed the value of X-Session-Token// Now: it prints [ BLACKLISTED ] insteadBlacklisted headers that you give to the constructor of a logging filter are matched ignoring case, as in LogConfig, so they’re hidden also when the HTTP client gives the header names in lower case, as the JDK client does.
JSON Schema validation and Jackson 3
Section titled “JSON Schema validation and Jackson 3”json-schema-validator now uses the networknt json-schema-validator 3.0.9 (was 2.0.8). It’s built on Jackson 3 (tools.jackson), so the module brings Jackson 3, and no longer Jackson 2 or Guava. The java-json-tools json-schema-validator, which REST Assured used for draft-03 and draft-04, is now optional. Which validator validates a schema:
| Schema | Validated by |
|---|---|
| Draft-06, draft-07, 2019-09 and 2020-12 | The networknt json-schema-validator |
Draft-04, and a schema without $schema, unless schemaVersion(..) picks another draft |
The java-json-tools json-schema-validator if it’s on the classpath, exactly as in REST Assured 6. Otherwise the networknt json-schema-validator, as draft-04 |
| Draft-03 | The java-json-tools json-schema-validator only |
Add the java-json-tools json-schema-validator to your test dependencies to validate draft-03 schemas, to use a JsonSchemaFactory or checked validation, or to keep exactly the behavior of REST Assured 6 for draft-04 and schemas without $schema:
<dependency> <groupId>com.github.java-json-tools</groupId> <artifactId>json-schema-validator</artifactId> <version>2.2.14</version> <scope>test</scope></dependency>Gradle:
testImplementation("com.github.java-json-tools:json-schema-validator:2.2.14")Without it, a draft-03 schema fails with a JsonSchemaValidationException whose message says which dependency to add, and so does settings().jsonSchemaFactory(). Code that uses JsonSchemaFactory needs it to compile, and checkedValidation(..) has no effect without it. The two validators word their messages differently, so a test that checks the message of a failed validation may need a change.
REST Assured prefers Jackson 3 for object mapping when it’s on the classpath, for example in as(Greeting.class) and body(greeting). So with json-schema-validator on the classpath, objects are now serialized and deserialized with Jackson 3, also when your project uses Jackson 2 and configures it with jackson2ObjectMapperFactory(..). Jackson 3 still reads the com.fasterxml.jackson.annotation annotations, but not the modules and settings of a Jackson 2 ObjectMapper, and has other defaults. To keep Jackson 2:
RestAssured.config = RestAssured.config().objectMapperConfig(objectMapperConfig().defaultObjectMapperType(ObjectMapperType.JACKSON_2));The same goes for RestAssuredMockMvc.config and RestAssuredWebTestClient.config. For one request, use as(Greeting.class, ObjectMapperType.JACKSON_2) or body(greeting, ObjectMapperType.JACKSON_2).
JsonPath prefers Jackson 3
Section titled “JsonPath prefers Jackson 3”JsonPath (json-path-gpath) now prefers Jackson 3 over Jackson 2 when both are on the classpath and nothing else is configured, like REST Assured’s object mapping. So response.jsonPath().getObject(..) and response.as(..) deserialize with the same Jackson. Before, getObject(..) used Jackson 2 and as(..) Jackson 3. Without Jackson, JsonPath uses the first of Gson, Johnzon and JSON-B (Yasson) on the classpath, as before.
Lotto lotto = get("/lotto").jsonPath().getObject("lotto", Lotto.class);// Before: deserialized with Jackson 2 when both Jacksons are on the classpath// Now: deserialized with Jackson 3, the same Jackson that as(..) usesresponse.jsonPath() now also follows ObjectMapperConfig.defaultObjectMapperType(..) of the request, except JAXB and JAKARTA_EE, which only map XML, and uses the JSON-B factory of the ObjectMapperConfig, like its Jackson and Gson factories. Before, it ignored them. To keep Jackson 2:
// For response.jsonPath(), and as(..) tooRestAssured.config = RestAssured.config().objectMapperConfig(objectMapperConfig().defaultObjectMapperType(ObjectMapperType.JACKSON_2));
// For the JsonPath instances that you create, such as JsonPath.from(json)JsonPath.config = new JsonPathConfig().defaultParserType(JsonParserType.JACKSON_2);OAuth 1 without scribejava
Section titled “OAuth 1 without scribejava”auth().oauth(..) and RestAssured.oauth(..) now sign the request themselves, with HMAC-SHA1 as RFC 5849 describes, so OAuth 1 no longer needs scribejava. If you added com.github.scribejava:scribejava-apis to your build for REST Assured, remove it. A scribejava jar that’s still on the classpath isn’t used.
The Authorization header and the query string are written as with scribejava 2.5.3. Where scribejava didn’t follow RFC 5849, REST Assured now does, so a server that follows the RFC can verify these signatures:
- The parameters are sorted by their encoded names and values. scribejava sorted the decoded ones, which gives another order for names or values with characters other than letters, digits,
-,.,_and~, such as non-ASCII names. - A query parameter value that contains
=is signed whole. scribejava signed only the part of the value before its first=. - In the signed URI, the scheme and host are in lower case, an empty path is
/, and user info and a fragment are left out. - An empty consumer secret is allowed. scribejava failed with an
OAuthSignatureException.
Non-backward compatible changes
Section titled “Non-backward compatible changes”- REST Assured no longer includes a path engine or Groovy, and
json-pathandxml-pathare renamed. See Upgrading to 7.0. - The
ObjectMapperConfigfactory getters andJsonConfig.numberReturnType()return therest-assured-commontypes, and Jackson 1 is removed. See Code that no longer compiles. - REST Assured checks that
rest-assured-common,json-path-gpathandxml-path-gpathhave the same major and minor version asrest-assured. - A JSON path that starts with
$.or$[, as in Jayway JsonPath, now starts at the root, like$alone. For example,$.store.book[0].titleis the same asstore.book[0].title. Before, such a path gavenullon an object. Keys that start with$, such as$schema, are unchanged, and$..is still not supported. - When a
MaporCollectionrequest body is written as JSON as it is, which happens when theObjectMappergiven tobody(object, mapper)returns one, the values that JSON has no type for, such as beans, records,java.timevalues,LocaleandFile, are now written by the configured object mapper, such as Jackson or Gson, instead of as an object of their bean properties. AURIis written as a string, like aURL, and anOptionalas its value, ornullwhen it’s empty. ANaNor infinite number fails with anIllegalArgumentException. - A cookie without value, such as
given().cookie("a"), is sent asa=instead ofa=null. - A
Cookieheader set withheader("Cookie", ..)is no longer dropped when the request also has cookies. Both are sent in oneCookieheader. - Headers whose names differ only in case, such as
X-Multiandx-multi, are both sent, under the name used first. Before, only the last one was sent. auth().digest(..)only answers a Digest challenge. Before, it also sent the password in clear text to a server that asked for Basic authentication. Usebasic(..)to answer both.- The
trustStore(path, password)andkeyStore(path, password)shortcuts no longer allow all host names. AddallowAllHostnames()to the SSL config to get the old behavior back. multiPart("name", bytes)with abyte[]sends the bytes as they are, asapplication/octet-stream, instead of as Base64 in atext/plainpart.- A
Stringpart without a charset is encoded in the charset of the multipart request (UTF-8 unless configured otherwise) instead of US-ASCII. - Sending a request no longer changes the specification it was sent from, so you can’t read back from it what the send set. See Specifications no longer change when sent or validated.
- The JDK’s HTTP client sends the requests instead of Apache HttpClient 4, which is no longer a dependency, nor are httpmime, httpcore, commons-codec and commons-logging. See How the JDK client differs from HttpClient 4.
- The APIs that take or return HttpClient 4 types, and
HTTPBuilder, are removed. See Removed APIs. - The SSL port settings,
sslConfig().port(..)andcertAuthSettings().port(..), are removed. See Removed APIs. - Timeouts, redirect errors,
HttpResponseExceptionandResponseParseExceptionare no longer HttpClient 4 exceptions. See Exceptions. - An HttpClient 4 parameter without a replacement fails the request. See HttpClient 4 parameters.
- In OSGi, the
rest-assuredbundle requires thehttp-client-jdkbundle. See OSGi and the HTTP client. - A request that sets a
Content-LengthorTransfer-Encodingheader fails, whichever client sends it. See Content-Length and Transfer-Encoding headers. RequestSpecBuilderandResponseSpecBuilderno longer copy the static settings, and what a builder computes while building uses its own config. See Spec builders no longer copy the static settings and What a builder computes while building.MockMvcRequestSpecBuilderandWebTestClientRequestSpecBuilderno longer copy the static settings, so a specification sent on its own needs its own MockMvc or WebTestClient instance. See The Spring specification builders.given(requestSpecification, responseSpecification)merges the specifications into the static settings, or those of the bound client, and appliesRestAssured.responseSpecification. See given(requestSpecification, responseSpecification).- Merging a response specification keeps the expectations that it doesn’t set, and its body expectations take the root path where they’re merged. See Merging a response specification.
- Merging specifications no longer adds a header or cookie that’s already there with the same value, or the same multipart part, result handler or post processor, again. See Equal headers and cookies are added once.
- A relative path of the page with the CSRF token is sent to the base URI, port and base path of the request. See The page with the CSRF token.
RestAssured.certificate(certURL, password)takes the SSL settings of the request when it’s sent. See certificate(certURL, password).- The logging filters follow the log config of the request when they log. See Logging.
- The root path methods deprecated since REST Assured 4.0.0, such as
root(..),appendRoot(..)andJsonPath.setRoot(..), are removed. UserootPath(..)and the other root path methods. See Removed APIs. json-schema-validatoruses the networknt json-schema-validator 3.0.9, which brings Jackson 3 instead of Jackson 2 and Guava. REST Assured prefers Jackson 3 for object mapping when it’s on the classpath, so if you use Jackson 2, setobjectMapperConfig().defaultObjectMapperType(ObjectMapperType.JACKSON_2). See JSON Schema validation and Jackson 3.- The java-json-tools json-schema-validator is optional in
json-schema-validator. Addcom.github.java-json-tools:json-schema-validator:2.2.14to validate draft-03 schemas, to use aJsonSchemaFactoryor checked validation, or to keep exactly the behavior of REST Assured 6 for draft-04 and schemas without$schema. Without it, those are validated as draft-04 by the networknt json-schema-validator. See JSON Schema validation and Jackson 3. JsonPathprefers Jackson 3 over Jackson 2 when both are on the classpath, andresponse.jsonPath()followsdefaultObjectMapperType(..)of the request. See JsonPath prefers Jackson 3.
Other changes
Section titled “Other changes”ResponseSpecBuilder.setConfig(RestAssuredConfig), whose config the response specification validates with. ALogConfigwithenableLoggingOfRequestAndResponseIfValidationFails()in it logs the request and the response when validation fails, or only the response withthen().spec(..), since the request was sent before.- The OpenRewrite recipe
io.restassured.UpgradeToRestAssured7, see Let OpenRewrite do it. It also replacesContextAuthenticationScheme, which is deprecated and removed in REST Assured 8.0.0, withAuthenticationScheme, which takes the same lambda. httpClientConfig().client(..), the system propertyrestassured.http.clientandcustomizeClient(..), see The HTTP client.- The
http-client-apache5module, which sends the requests with Apache HttpClient 5, see Apache HttpClient 5. With it,connectionConfig().closeIdleConnectionsAfterEachResponseAfter(..)closes the idle connections of a client kept withreuseHttpClientInstance(), as in 6.x. - Basic and Digest authentication, OAuth, redirects and the kept cookies of
keepResponseCookies(true)are handled by REST Assured itself and work the same with every client. Abyte[]body is sent again when it answers a challenge or follows a 307 or 308 redirect. - Form authentication uses the first text input and the first password input of a login page that has several, and finds them whatever the case of their
type. - Deserializing an XML body to a generic type with the JAXB or Jakarta EE object mapper fails with an
IllegalArgumentExceptionthat says JAXB can only deserialize to a class, instead of Groovy’sMissingMethodException. RestAssuredClient,RestAssured.given(client),useClient(..),resetClient()andclient(), the same forRestAssuredMockMvcandRestAssuredWebTestClientwithRestAssuredMockMvcClientandRestAssuredWebTestClientClient, andGiven(client)in the Kotlin and Scala extensions. See Clients for parallel tests.- Sending a
RestAssuredMockMvcorRestAssuredWebTestClientspecification no longer changes it. Each send gets its own logs, so withenableLoggingOfRequestAndResponseIfValidationFails()a specification sent several times, or from many threads, no longer logs the requests of the other sends. - The request for the page with the CSRF token of
given().csrf(..)andCsrfConfig.csrfTokenPath(..)uses the config and the proxy of the request, as with form authentication. JsonPath.config,XmlPath.configandJsonSchemaValidator.settingsare standalone settings: they’re never taken from a request or a client, andusing(..)sets them for oneJsonPath,XmlPathor matcher.- Hamcrest 3.0 (was 2.2), which has the same API. In OSGi, the
rest-assuredbundle accepts Hamcrest 2.2 and 3.x. Hamcrest 3.0’s own bundle importsorg.hamcrestwithout a version, so if an older Hamcrest bundle is installed too, such as the 1.3 bundle that pax-exam’s JUnit 4 support uses, install Hamcrest 3.0 first, at a lower start level. Otherwise it binds to the older package and therest-assuredbundle doesn’t resolve. json-path-jaywaydepends on Jayway JsonPath 3.0.0 (was 2.10.0), which needs Java 17. It still works with Jayway JsonPath 2.8.0 and later, so a version that your build manages, such as Spring Boot 4’s 2.10.0, keeps working. In OSGi, Jayway JsonPath 3.0.0 requires the Jackson 3 bundles, see OSGi.json-path-gpath,xml-path-gpathandrest-assured-alldepend on Groovy 5.1.3 (was 5.0.8) and work with Groovy 4.0, 5.x and 6.x. In OSGi, they import Groovy 4.0 up to, but not including, 7.0, instead of 5.x only. See Groovy versions.scala-extensionsdepends onscala3-library_33.3.9 (was 3.3.8), the latest Scala 3.3 LTS. It keeps working with every newer Scala 3, and CI also tests it with the latest Scala 3 (3.10.0).- The optional dependencies that REST Assured is compiled against, which you bring yourself, are upgraded: Jackson 2 (
jackson-databind,jackson-coreandjackson-module-kotlin) to 2.22.4, Jackson 3 (tools.jackson) to 3.2.4, Gson to 2.15.0, Jakarta XML Binding to 4 (jakarta.xml.bind-api4.0.5 andjaxb-runtime4.0.9, was 3.0.1 and 3.0.2), Spring Framework inspring-mock-mvc,spring-web-test-clientand their Kotlin extensions to 7.0.10, Spring Security inspring-mock-mvcto 7.1.2 (was 6.5.11), and the Jakarta EE Web API inspring-web-test-client-kotlin-extensionsto 11.0.0. In OSGi, the optional imports ofjakarta.xml.bindaccept Jakarta XML Binding 3.x and 4.x (was 3.x only), and the optional import ofcom.google.gsonaccepts Gson 2.15 or later (was 2.14 or later). - The OSGi bundles now require a Java 17 execution environment (
osgi.eeJavaSE 17), which every Java 17 framework provides. See OSGi. spring-web-test-clientno longer depends onorg.synchronoss.cloud:nio-multipart-parser, which Spring Framework 6 and later don’t use, so it,nio-stream-storageandslf4j-api1.7.12 leave your classpath.- OAuth 1 (
auth().oauth(..)) no longer needs scribejava, and in OSGi therest-assuredbundle no longer importscom.github.scribejava. Signatures now follow RFC 5849 where scribejava didn’t. See OAuth 1 without scribejava. - A config set on a request specification, such as
new RequestSpecBuilder().setConfig(..), now also applies toexpect()assertions, andgiven(requestSpecification, responseSpecification)with logging if validation fails logs the request too, not only the response. - Every response log hides the blacklisted headers, not only
then().log(). See Logging.LogConfig.blacklistDefaultSensitiveHeaders()no longer addsAuthorization,Proxy-AuthorizationandCookieto the blacklist of theLogConfigit’s called on, only to the newLogConfigthat it returns. - Fixes for
auth().oauth2(accessToken, OAuthSignature.QUERY_STRING),closeIdleConnectionsAfterEachResponse()with large bodies,dontCloseIdleConnectionsAfterEachResponse(),reuseHttpClientInstance()after a response whose body was never read,RestAssured.keyStore(password),ProxySpecification.withScheme(..), multipart requests inRestAssuredWebTestClient, and pretty printing of a JSON string with an escaped backslash followed byu.
See the change log for all changes.

