REST Assured 6.2.0
Highlights
Section titled “Highlights”-
The
rest-assuredmodule is now written entirely in Java and no longer depends on Groovy (org.apache.groovy:groovyandgroovy-xml) directly. Groovy still comes transitively throughjson-pathandxml-path, which use it for GPath, so the dependencies on your classpath are unchanged. Therest-assuredandrest-assured-commonOSGi bundles no longer import the Groovy packages, andrest-assured-commonno longer depends on Groovy at all. -
Fixes for tests that run in parallel. Changing the static configuration (for example
RestAssured.filters(..),replaceFiltersWith(..),registerParser(..)orunregisterParser(..)) while another thread callsgiven()could fail with aConcurrentModificationExceptionor lose filters.given()no longer changes any shared state, and the public static fields ofRestAssured,RestAssuredMockMvcandRestAssuredWebTestClient, as well asJsonPath.config,XmlPath.configandJsonSchemaValidator.settings, are nowvolatile, so a value set before the tests start is seen by every thread.HttpClientConfig.reuseHttpClientInstance()no longer creates more than oneHttpClientwhen several threads send their first request at the same time. The javadoc ofRestAssured,RestAssuredMockMvcandRestAssuredWebTestClientnow explains how to use the static configuration with parallel tests. In short:- Set the static defaults once, before the tests start running in parallel (for example in a
@BeforeAllmethod), and don’t change them while requests are being made. - Put values that differ between tests in the request specification instead, for example
given().baseUri("http://localhost").port(8081)orgiven(spec). - Don’t execute the same request specification from several threads at once. Start a new one with
given()in each thread. - Filters added with
RestAssured.filters(..)are shared by all requests and must be thread safe. - The reused
HttpClientfromreuseHttpClientInstance()is reconfigured by every request, so it still must not be used by requests that run at the same time.
- Set the static defaults once, before the tests start running in parallel (for example in a
-
auth().certificate(certURL, password)andRestAssured.certificate(certURL, password)now send the client certificate in the file to servers that ask for one (mutual TLS), as documented. Before, the file was only used as trust store. In the same way, a key store configured withkeyStore(..)but without a trust store is now used, where it used to be ignored. Both are non-backward compatible, see SSL and certificates.given().auth().certificate("/client-keystore.jks", "password").when().get("https://localhost:8443/secured").then().statusCode(200); -
JsonPath and JSON body assertions can read a key that contains a colon with the dot notation. This used to fail with “Invalid JSON expression”:
get("/status").then().body("message.ErrorCode:", equalTo(0));A colon in a ternary (
a ? b : c) or Elvis (a ?: b) expression, in a closure or in a quoted string is left as is. -
Request logging no longer prints the content of a multipart
Filewhose mime type isn’t text, such asapplication/octet-stream(the default) orimage/png. Since 5.5.0 the whole file was dumped to the log. Such a part is now logged as the path of the file, as before 5.5.0. A file with a text mime type (text/*, JSON, XML or HTML) is still logged, and is now read with the charset of the part (or of its mime type), or UTF-8 if none is given. -
Clearer errors. Many failures that used to surface as Groovy exceptions (
groovy.lang.MissingMethodException,GroovyRuntimeException,GroovyCastException), aNullPointerExceptionor aStackOverflowErrornow either work or fail with an exception that says what is wrong. For example:- Sending a request directly from a specification built by
RequestSpecBuilder.build()fails with anIllegalStateExceptionthat tells you to usegiven().spec(spec)orgiven(spec). - Sending a request directly from a specification built by
ResponseSpecBuilder.build()fails with anIllegalStateExceptionthat tells you how to add it to a request. - A failed body path assertion on a body that the path can’t navigate shows the response body instead of the literal text
$contentParser. - Form authentication names the additional field that isn’t on the login page, and says when a redirect to the login page has no Location header.
- A key store given as a
KeyStorewithout a password, whose private key needs one, fails with a message that says how to set the password. - When SSL settings can’t be applied to an
httprequest that is redirected tohttps, theSSLExceptionends with the message of the cause (for example aFileNotFoundExceptionfor a missing trust store) instead of “null”.
- Sending a request directly from a specification built by
Non-backward compatible changes
Section titled “Non-backward compatible changes”Request bodies and content types
Section titled “Request bodies and content types”-
A request body sent with
noContentType()no longer gets a Content-Type header. Before, a binary body (byte[],InputStreamorFile) gotContent-Type: */*, and when an expected response content type was set (for exampleexpect().contentType(ContentType.JSON)) that content type was sent as the request Content-Type. If the server needs a Content-Type, set it withcontentType(..)instead ofnoContentType():given().contentType(ContentType.BINARY).body(bytes).when().post("/upload"); // instead of given().noContentType().body(bytes) -
A request body that isn’t a
String(for example abyte[],InputStreamorFile) sent with content-typeapplication/x-www-form-urlencodednow fails with anIllegalArgumentExceptionexplaining that a form url-encoded body must be aString. Before, it failed with agroovy.lang.MissingMethodException. Send the body as aString, or useformParam(..). -
A
groovy.lang.Writablerequest body sent with a text content-type is now sent as itstoString()instead of what itswriteTo(..)method writes. Groovy’s own Writables (such asJsonBuilderandGString) produce the same output either way. A GroovyWritableFile(file.asWritable()) is now sent like any otherFile, read with the charset of the request content-type. If your ownWritablewrites something other than itstoString(), write it to aStringyourself and send that. -
EncoderConfig.defaultCharsetForContentType(charset, contentType)now only sets the default charset of the given content type. It used to also change the default content charset (defaultContentCharset()), so the charset was used for every content type without a charset of its own. For example, afterencoderConfig().defaultCharsetForContentType("UTF-16", ContentType.JSON)a plain text body was sent astext/plain; charset=UTF-16and is now sent astext/plain; charset=ISO-8859-1. If you relied on this, also calldefaultContentCharset(..):encoderConfig().defaultCharsetForContentType("UTF-16", ContentType.JSON).defaultContentCharset("UTF-16")The same change was made to
DecoderConfig, see Responses.
Form authentication
Section titled “Form authentication”These changes apply when REST Assured reads the login page to find the form. A form action given to FormAuthConfig is still used as it is.
- Form authentication now fails with an
IllegalArgumentExceptionthat names the field when an additional input field given toFormAuthConfig.withAdditionalField(s)(..)isn’t on the login page. Such a field used to be sent with the value[]. Correct the field name, or remove it fromwithAdditionalField(s)(..). - A login form with an empty action (
action="") is now posted to the URL of the login page, like a browser does, instead of to/. - A relative form action is now resolved against the URL of the login page (or its
<base href>), like a browser does. An action without a leading slash used to be resolved against the root, so for exampleaction="login"on the page/secured/pagewas posted to/loginand is now posted to/secured/login.
If your server expects the old target in either case, give the form action in FormAuthConfig:
given().auth().form("John", "Doe", new FormAuthConfig("/login", "j_username", "j_password")). ..SSL and certificates
Section titled “SSL and certificates”-
auth().certificate(certURL, password)andRestAssured.certificate(certURL, password), with or withoutCertificateAuthSettings, now use the file atcertURLas key store, as documented, so the client certificate in it is sent to servers that ask for one (mutual TLS). Before, the file was only used as trust store, which it still is. This means that:- A private key in the file is now offered to any server that asks for a client certificate, also where it’s optional (such as nginx with
ssl_verify_client optional). Such a server may then reject a certificate it doesn’t trust. - The private key must have the password of the file.
To avoid this, or to use the JVM’s default trust store, use the overload that takes a separate trust store and key store. An empty path means no key store, or the default trust store:
// Use the file only as trust store, as before (no client certificate is sent)RestAssured.authentication = certificate("/truststore.jks", "password", "", "", certAuthSettings());certificate(..)with five arguments returns anAuthenticationScheme, so you can also pass it toRequestSpecBuilder.setAuth(..). - A private key in the file is now offered to any server that asks for a client certificate, also where it’s optional (such as nginx with
-
A key store configured without a trust store (with
keyStore(..)on a request specification,RequestSpecBuilder,RestAssuredorSSLConfig) is now used, so REST Assured offers its client certificate to a server that asks for one. Before, the key store was loaded but ignored unless a trust store was configured too, so a server that requires a client certificate rejected the connection. How the server certificate is validated is unchanged (the JVM’s default trust when no trust store is configured). Things to look out for:- A server that only requests a client certificate (for example nginx with
ssl_verify_client optional) and doesn’t trust it now rejects a connection that used to work. If you don’t need a client certificate for that server, remove thekeyStore(..)call. - A key store whose private key can’t be read with the key store password now fails the request instead of being ignored.
- A key store given as a
KeyStorewithout a password, whose private key needs one, fails with anIllegalArgumentExceptionthat says how to set the password, for example:
given().config(RestAssured.config().sslConfig(sslConfig().keyStore(keyStore).keyStore("password"))). .. - A server that only requests a client certificate (for example nginx with
Specifications and merging
Section titled “Specifications and merging”-
Merging a request specification with
spec(..),specification(..)orRequestSpecBuilder.addRequestSpecification(..)no longer resets values that the merged specification never set. The base URI, base path, port, authentication scheme, request body, proxy, HTTP method, path (together with its unnamed path parameters), whether url encoding is enabled, whether a Content-Type is allowed (noContentType()) and whether CSRF is disabled (disableCsrf()) now survive a merge, and are only overwritten when the merged specification sets them. Values that the merged specification only got from the defaults (such asRestAssured.baseURI,RestAssured.port,RestAssured.authenticationorRestAssured.requestSpecification) don’t overwrite anything either. For example:given().auth().oauth2("token").spec(specWithOnlyAHeader). .. // now sends the Authorization header, before it sent nonegiven().baseUri("http://host").port(8081).spec(specWithOnlyAHeader). .. // now uses http://host:8081, before it used RestAssured.baseURI and RestAssured.portThis also affects a plain
given(), which mergesRestAssured.requestSpecification: it now uses the current values ofRestAssured.baseURI,RestAssured.port,RestAssured.basePathandRestAssured.urlEncodingEnabledinstead of the values they had whenRestAssured.requestSpecificationwas built, unless that specification set them itself. To remove the authentication with a merged specification, set it to none explicitly:RequestSpecification noAuth = new RequestSpecBuilder().setAuth(new ExplicitNoAuthScheme()).build(); -
Sending a request directly from a request specification created by
RequestSpecBuilder.build()(for examplespec.when().get("/x")orspec.post()) now fails with anIllegalStateExceptionthat tells you to usegiven().spec(spec)orgiven(spec), instead of aNullPointerException.then(),expect()andresponse()on such a specification also throw this exception, where they returnednullbefore. The specification isn’t changed by the failed call.given(spec).when().get("/x"); // instead of spec.when().get("/x") -
given()no longer writes the session id into theRestAssured.configfield, orRestAssured.defaultParserinto the global parser registry. WhileRestAssured.sessionIdis set,RestAssured.config()returns the config with the session id, but theRestAssured.configfield doesn’t contain it, also not after a request. Sogiven().config(RestAssured.config)no longer sends the static session id:given().config(RestAssured.config()). .. // instead of given().config(RestAssured.config)Setting
RestAssured.sessionIdorRestAssured.defaultParserback tonullnow takes effect withoutRestAssured.reset(). A list returned byRestAssured.filters()beforereplaceFiltersWith(..)keeps showing the old filters. -
Sending a request directly from a response specification created by
ResponseSpecBuilder.build()(for exampleresponseSpec.when().get("/x")) now fails with anIllegalStateExceptioninstead of aNullPointerException.when(),given(),with()andrequest()on such a specification also throw this exception, where they returnednullbefore. Using the specification in a request, or asRestAssured.responseSpecification, works as before.given(requestSpec, responseSpec).when().get("/x"); // or given().spec(requestSpec).expect().spec(responseSpec) -
Using a
RequestSpecificationthat wasn’t created by REST Assured withspec(..)orspecification(..)on a request specification now fails with anIllegalArgumentExceptioninstead of agroovy.lang.MissingMethodException. -
Using a
ResponseSpecificationthat wasn’t created by REST Assured, withspec(..)orspecification(..)on a response specification or asRestAssured.responseSpecification, now fails with anIllegalArgumentExceptioninstead of agroovy.lang.MissingMethodException.
Custom extensions written against internal or Groovy types
Section titled “Custom extensions written against internal or Groovy types”These changes only affect you if you implement or call REST Assured’s SPI or io.restassured.internal classes, or rely on them being Groovy objects.
- A Groovy closure used as request body now fails with an
IllegalArgumentException. This could only happen when a customObjectMapperreturned aClosurefromserialize(..), in which case the closure was run as a builder (aJsonBuilderfor JSON, aStreamingMarkupBuilderfor XML and HTML, or with aWriterorOutputStreamfor text and binary content). Return aString,byte[]orInputStreamfrom theObjectMapperinstead. - The authentication schemes in
io.restassured.authentication(BasicAuthScheme,CertAuthScheme,ExplicitNoAuthScheme,FormAuthScheme,NTLMAuthScheme,NoAuthScheme,OAuth2Scheme,OAuthScheme,PreemptiveBasicAuthSchemeandPreemptiveOAuth2HeaderScheme) and theAuthenticationSchemeinterface are now written in Java, and the classes no longer implementgroovy.lang.GroovyObject. Their constructors, getters and setters are unchanged. A customAuthenticationSchemewritten in Java can now get the authentication configuration fromHTTPBuilder.getAuth(). io.restassured.internal.http.HTTPBuilder, which a customAuthenticationSchemereceives inauthenticate(..), no longer uses Groovy closures. Itsget(..)methods, therequest(..)overloads that took aClosure,getHandler()and thepost(..)andpatch(..)overloads without a response handler are removed.post(..),patch(..)andrequest(..)now take anHttpResponseHandleror aRequestConfigurer, and response handlers always take two arguments (the response and its parsed content).io.restassured.internal.RestAssuredResponseOptionsGroovyImplis removed and its logic now lives inRestAssuredResponseOptionsImpl, sogetGroovyResponse(),setGroovyResponse(..)and the protectedgroovyResponsefield ofRestAssuredResponseOptionsImplare gone. This affects code that calls these methods or subclasses the internal response classes. A subclass that overrides a method such asasString()now also sees that override used bypath(..),jsonPath(),as(..)and the other methods that read the body.io.restassured.assertion.HeaderMatcher,DetailedCookieAssertion,ResponseTimeMatcherandStreamVerifierare now written in Java and no longer implementgroovy.lang.GroovyObject.HeaderMatcher.validateHeader(..),DetailedCookieAssertion.validateCookies(..)andResponseTimeMatcher.validate(..)now returnMap<String, Object>instead ofObject,HeaderMatcher.validateHeader(..)takes aResponse, andHeaderMatcher.getMatcher()andget/setResponseAwareMatcher(..)are now generically typed (Matcher<?>andResponseAwareMatcher<Response>).io.restassured.internal.matcher.xml.XmlXsdMatcher(the return type ofRestAssuredMatchers.matchesXsd(..)) andXmlDtdMatcherare now written in Java and no longer implementgroovy.lang.GroovyObject.
Responses
Section titled “Responses”-
path(..)on a response with an HTML content type (for exampleget("/page.html").path("html.head.title")orthen().extract().path(..)) now returns the value the path points to, like it does for JSON and XML responses and likebody("html.head.title", ..)does. Before, it returned theXmlPathitself, so extracting the value to aStringfailed with aClassCastException. UsehtmlPath()to get anXmlPathfor an HTML response:String title = get("/page.html").path("html.head.title");XmlPath html = get("/page.html").htmlPath(); // instead of get("/page.html").path(..) to get the XmlPath -
DecoderConfig.defaultCharsetForContentType(charset, contentType)now only sets the default charset of the given content type. It used to also change the default content charset (defaultContentCharset()), so a response without a charset in its content-type was decoded with that charset even when its content type had no charset of its own. For example, afterdecoderConfig().defaultCharsetForContentType("UTF-16", ContentType.JSON)atext/plainresponse without a charset was decoded as UTF-16 and is now decoded with the default content charset. If you relied on this, also calldefaultContentCharset(..):decoderConfig().defaultCharsetForContentType("UTF-16", ContentType.JSON).defaultContentCharset("UTF-16") -
matchesXsdInClasspath(..)andmatchesDtdInClasspath(..)(inRestAssuredMatchers,RestAssuredMockMvcMatchersandRestAssuredWebTestClientMatchers) now fail with anIllegalArgumentExceptionsaying that the XSD or DTD couldn’t be found in classpath, instead of agroovy.lang.GroovyRuntimeException(“Ambiguous method overloading”). The XSD and DTD matchers now also fail with anIllegalArgumentExceptionwhen asked to match something that isn’t aString, for examplebody("some.path", matchesXsd(..))where the path returns an element that has child elements, instead of a Groovy exception.
Other changes
Section titled “Other changes”- Upgraded the dependencies of the published modules. scala-library in
scala-supportgoes from 2.13.0 to 2.13.18, which fixes CVE-2022-36944. Also upgraded: scala3-library_3 inscala-extensionsto 3.3.8, Jackson 2 to 2.22.3, Jackson 3 (tools.jackson) to 3.2.3, Gson to 2.14.0, Guava injson-schema-validatorto 33.7.2-jre, commons-lang3 to 3.21.0, Yasson to 3.0.5 (now with its new group idorg.eclipse.yasson), Parsson to 1.1.9, Johnzon to 2.2.0 and jaxb-impl to 2.3.9 (#1986). - Fixed
spring-web-test-client-kotlin-extensionsbringingjakarta.servlet-api,jakarta.jakartaee-web-apiand about 20 other Jakarta EE API jars to your runtime classpath. They are nowprovided, like inspring-mock-mvc-kotlin-extensions(#1992). - Upgraded Groovy from 5.0.3 to 5.0.8 and Apache HttpClient (
httpclientandhttpmime) from 4.5.13 to 4.5.14 (#1971). - Fixed PATCH requests with form parameters or multipart content, which were sent without their query parameters (#1001).
- Fixed sending a
Stringbody or form parameters withnoContentType(), which failed unless an expected response content type was set. The body is now sent as text in the default content charset, so theencodeContentTypeAs("*/*", ContentType.TEXT)workaround is no longer needed (#1947). - Fixed a request hanging forever when its path contains a
{that is never closed, for exampleget("/x{b"). An unclosed{is now kept as literal text (#1935). - Fixed a
byte[]multipart being sent empty the second time a request specification is used (#1938). - Fixed a multipart boundary that contains
=, for example a JavaMail-style boundary such as----=_Part_0_123, being replaced or cut short. The boundary is now used as given (#1948). - Fixed
multiPart(String, Object)with aStringand no request content type. It now adds atext/plainpart (#1930). - Fixed
keyStore(KeyStore)on a request specification andRequestSpecBuilder.setKeyStore(KeyStore), which always failed.SSLConfiggets akeyStore(KeyStore)method.relaxedHTTPSValidation()only uses a key store given by path, not aKeyStoreobject (#1930). - Form authentication fixes:
- Requests with path parameters, for example
given().auth().form("John", "Doe").get("/secured/{id}", 1), no longer fail with “Invalid number of path parameters”, and the login page is no longer URL encoded twice (#1940). - A login form without an action attribute is posted to the URL of the login page instead of failing with a
NullPointerException(#1941). - A
SessionFiltersubclass is passed on to the login request (#1941). - An additional field whose input tag has no value attribute is sent without a value instead of failing (#1921).
- A form action read from the login page is no longer URL encoded twice, and an absolute form action’s path and query are posted to the host of the request (#1954).
- Requests with path parameters, for example
- Fixed
CsrfConfig.csrfMetaTagName(..), which also set the CSRF input field name (#1945). - Fixed
LogConfig.defaultStream(..), which always turned pretty printing back on (#1946). - Fixed merging a request specification that has unnamed path parameters, for example the one a filter receives for
get("/users/{id}", 1)(#1931). - Fixed a root path with more than one placeholder left for a later
withArgs(..), which failed with aMissingFormatArgumentException.body(withArgs(..), matcher)no longer adds a trailing dot to the path in failure messages (#1949). - Fixed
FilterContext.send(..)for TRACE and custom HTTP methods such asrequest("PURGE", "/x")(#1929). - Fixed
getResponseContentType()on the response specification that filters receive, which always failed with aStackOverflowError(#1928). - Fixed
asByteArray()on a response whose body was replaced by anInputStream, for example by a filter (#1926). - Fixed
sessionId()on Spring MockMvc, Spring WebTestClient andResponseBuilderresponses, andheader(name)on a response without headers, which now returnsnull(#1944). - Fixed a
ResponseAwareMatcherin a staticRestAssuredMockMvc.responseSpecificationorRestAssuredWebTestClient.responseSpecificationfailing when it callspath(..), and ignoring the configured session id name (#1944). - XSD and DTD matcher fixes:
matchesDtd(URL)always failed with aGroovyCastException(#1920).- The same matcher can now be used more than once, for example in a shared
ResponseSpecification. AnInputStreamorReadergiven to these matchers is now read and closed when the matcher is created (#1936). matchesXsdInClasspath(..)andmatchesDtdInClasspath(..)also look in the class loader that loaded REST Assured, so they work on threads without a usable context class loader (#1937).
- Fixed JsonPath and JSON body expectations failing with
ClassNotFoundException: groovy.lang.Scriptin an OSGi container, or whenever the thread context class loader can’t see Groovy (#1933). - Non-
Stringnames and values that used to fail with a Groovy exception are now converted to their string form, so1becomes"1": parameter names inparams(Map),queryParams(Map),formParams(Map)andpathParams(Map)(#1927), cookie values incookies(..)(#1930), header and cookie names inheaders(..)andcookies(..)(#1931), header names in header expectations (#1919) and body expectation paths (#1928). - A list given as path, or as path arguments to
body(..),rootPath(..),root(..)orappendRootPath(..), whose elements aren’t all created bywithArgs(..), now fails with anIllegalArgumentException(#1928). ACertAuthSchemekey store or trust store path that is neither aStringnor aFilealso fails with anIllegalArgumentException(#1925).
See change log for more details.

