Repository: rest-assured/rest-assured
Pull request #1576 (2022-04-03 00:20:20)

setup-java v3


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9868b8bd..a2b3f10e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,11 +15,12 @@ jobs:
     name: Test JDK ${{ matrix.java }}, ${{ matrix.os }}
 
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - name: Set up JDK
-        uses: actions/setup-java@v1
+        uses: actions/setup-java@v3
         with:
           java-version: ${{ matrix.java }}
+          distribution: 'zulu'
       - name: Verify with Maven
         run: mvn verify -B --file pom.xml
 
diff --git a/README.md b/README.md
index 5731b94d..98a76e46 100644
--- a/README.md
+++ b/README.md
@@ -11,9 +11,9 @@ languages into the Java domain.
 
 
 ## News
+* 2022-05-24: REST Assured 5.1.0 is released. The biggest change is that the [Spring MockMvc Module](https://github.com/rest-assured/rest-assured/wiki/Usage#spring-mock-mvc-module) now supports Spring Framework 6 and Jakarta API's. See [change log](https://raw.githubusercontent.com/rest-assured/rest-assured/master/changelog.txt) for more details.
+* 2022-04-06: REST Assured 5.0.1 is released that fixes a bug introduced in the previous version. See [change log](https://raw.githubusercontent.com/rest-assured/rest-assured/master/changelog.txt) for more details.
 * 2022-03-25: REST Assured 5.0.0 is released which adds support for Groovy 4. See [release notes](https://github.com/rest-assured/rest-assured/wiki/ReleaseNotes50) and [change log](https://raw.githubusercontent.com/rest-assured/rest-assured/master/changelog.txt) for more details.
-* 2022-02-11: REST Assured 4.5.1 is released. It fixes a bug that accidentally required both Jakarta EE _and_ JAXB to be in the classpath at the same time under certain conditions. See [change log](https://raw.githubusercontent.com/rest-assured/rest-assured/master/changelog.txt) for more details.
-* 2022-01-21: REST Assured 4.5.0 is released. It adds support for [Jakarta EE](https://github.com/rest-assured/rest-assured/wiki/Usage#jakarta-ee) for XML object mapping. It also makes [JAXB](https://github.com/rest-assured/rest-assured/wiki/Usage#jaxb) optional. See [release notes](https://github.com/rest-assured/rest-assured/wiki/ReleaseNotes45) and [change log](https://raw.githubusercontent.com/rest-assured/rest-assured/master/changelog.txt) for more details.
 
 [Older News](https://github.com/rest-assured/rest-assured/wiki/OldNews)
 
@@ -79,11 +79,11 @@ REST Assured supports any HTTP method but has explicit support for *POST*, *GET*
 
 * [Getting started](https://github.com/rest-assured/rest-assured/wiki/GettingStarted)
 * [Usage Guide](https://github.com/rest-assured/rest-assured/wiki/Usage) (click [here](https://github.com/rest-assured/rest-assured/wiki/Usage_Legacy) for legacy documentation)
-* [Javadoc](http://www.javadoc.io/doc/io.rest-assured/rest-assured/5.0.0)
-* [Rest Assured Javadoc](http://static.javadoc.io/io.rest-assured/rest-assured/5.0.0/io/restassured/RestAssured.html)
-* [Rest AssuredMockMvc Javadoc](http://static.javadoc.io/io.rest-assured/spring-mock-mvc/5.0.0/io/restassured/module/mockmvc/RestAssuredMockMvc.html)
-* [XmlPath Javadoc](http://static.javadoc.io/io.rest-assured/xml-path/5.0.0/io/restassured/path/xml/XmlPath.html)
-* [JsonPath Javadoc](http://static.javadoc.io/io.rest-assured/json-path/5.0.0/io/restassured/path/json/JsonPath.html)
+* [Javadoc](http://www.javadoc.io/doc/io.rest-assured/rest-assured/5.1.0)
+* [Rest Assured Javadoc](http://static.javadoc.io/io.rest-assured/rest-assured/5.1.0/io/restassured/RestAssured.html)
+* [Rest AssuredMockMvc Javadoc](http://static.javadoc.io/io.rest-assured/spring-mock-mvc/5.1.0/io/restassured/module/mockmvc/RestAssuredMockMvc.html)
+* [XmlPath Javadoc](http://static.javadoc.io/io.rest-assured/xml-path/5.1.0/io/restassured/path/xml/XmlPath.html)
+* [JsonPath Javadoc](http://static.javadoc.io/io.rest-assured/json-path/5.1.0/io/restassured/path/json/JsonPath.html)
 * [Release Notes](https://github.com/rest-assured/rest-assured/wiki/ReleaseNotes)
 * [FAQ](https://github.com/rest-assured/rest-assured/wiki/FAQ)
 
diff --git a/changelog.txt b/changelog.txt
index a2b798cd..d4aacbd8 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,5 +1,21 @@
-Changelog next version
+Changelog next-version
 ----------------------
+* Added support for Spring Framework 6.0 in Spring WebClientTest module
+* Overwrite responseLogDetail when merging ResponseSpecificationImpl (issue 1493) (thanks to petur for pull request)
+* Fix: rootPath does not allow chaining paths with leading operators (issue 1577) (thanks to Roman Mitasov for pull request)
+* Fix: body(InputStream) does not work (issue 1480) (thanks to fedinskiy for pull request)
+* Fix: jsonPath - return null if list(array) does not exists in input document (issue 1544) (thanks to Oleksandr Kulychok for pull request)
+* Upgraded gson from 2.6.2 to 2.9.0
+
+Changelog 5.1.0 (2022-05-24)
+----------------------------
+* Upgrading kotlin to 1.6.21
+* Moved package io.restassured.assertion to io.restassured.internal.assertion since the classes in this package should be internal.
+  Not all files have been moved yet, but refactoring will continue in the next release.
+* Added support for Spring Framework 6.0 and Jakarta API's in Spring MockMvc module.
+
+Changelog 5.0.1 (2022-04-06)
+----------------------------
 * Fixed so that with RestAssuredHttpBuilder no longer requires ResponseSpecificationImpl but rather a FilterableResponseSpecification
 
 Changelog 5.0.0 (2022-03-25)
diff --git a/dist/dist-json-path/pom.xml b/dist/dist-json-path/pom.xml
index 223dec25..0ddef3ef 100644
--- a/dist/dist-json-path/pom.xml
+++ b/dist/dist-json-path/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>dist</artifactId>
         <groupId>io.rest-assured</groupId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/dist/dist-json-schema-validator/pom.xml b/dist/dist-json-schema-validator/pom.xml
index 854f8d27..41fc6908 100644
--- a/dist/dist-json-schema-validator/pom.xml
+++ b/dist/dist-json-schema-validator/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>dist</artifactId>
         <groupId>io.rest-assured</groupId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/dist/dist-kotlin-extensions/pom.xml b/dist/dist-kotlin-extensions/pom.xml
index ff0c88aa..dea3d732 100644
--- a/dist/dist-kotlin-extensions/pom.xml
+++ b/dist/dist-kotlin-extensions/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>dist</artifactId>
         <groupId>io.rest-assured</groupId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/dist/dist-rest-assured/pom.xml b/dist/dist-rest-assured/pom.xml
index a27af539..0ef115cd 100644
--- a/dist/dist-rest-assured/pom.xml
+++ b/dist/dist-rest-assured/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>dist</artifactId>
         <groupId>io.rest-assured</groupId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/dist/dist-scala-support/pom.xml b/dist/dist-scala-support/pom.xml
index 933dd9e7..3fe0fd1b 100644
--- a/dist/dist-scala-support/pom.xml
+++ b/dist/dist-scala-support/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>dist</artifactId>
         <groupId>io.rest-assured</groupId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/dist/dist-spring-mock-mvc/pom.xml b/dist/dist-spring-mock-mvc/pom.xml
index 130c3e89..bd80f499 100644
--- a/dist/dist-spring-mock-mvc/pom.xml
+++ b/dist/dist-spring-mock-mvc/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>dist</artifactId>
         <groupId>io.rest-assured</groupId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/dist/dist-xml-path/pom.xml b/dist/dist-xml-path/pom.xml
index a72a162e..e30134b2 100644
--- a/dist/dist-xml-path/pom.xml
+++ b/dist/dist-xml-path/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>dist</artifactId>
         <groupId>io.rest-assured</groupId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/dist/pom.xml b/dist/pom.xml
index c392f1bf..74fe39bd 100644
--- a/dist/pom.xml
+++ b/dist/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>rest-assured-parent</artifactId>
         <groupId>io.rest-assured</groupId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/examples/kotlin-example/pom.xml b/examples/kotlin-example/pom.xml
index f1128b33..1e4870ba 100644
--- a/examples/kotlin-example/pom.xml
+++ b/examples/kotlin-example/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <groupId>io.rest-assured.examples</groupId>
         <artifactId>example-parent</artifactId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -78,13 +78,13 @@
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>rest-assured</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>kotlin-extensions</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/examples/pom.xml b/examples/pom.xml
index af54889a..aeeba521 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>rest-assured-parent</artifactId>
         <groupId>io.rest-assured</groupId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <groupId>io.rest-assured.examples</groupId>
diff --git a/examples/scala-example/pom.xml b/examples/scala-example/pom.xml
index 5bc58cf8..f2dbe716 100644
--- a/examples/scala-example/pom.xml
+++ b/examples/scala-example/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <groupId>io.rest-assured.examples</groupId>
         <artifactId>example-parent</artifactId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -76,13 +76,13 @@
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>rest-assured</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>scala-support</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/examples/scala-mock-mvc-example/pom.xml b/examples/scala-mock-mvc-example/pom.xml
index 16576617..569c2ba2 100644
--- a/examples/scala-mock-mvc-example/pom.xml
+++ b/examples/scala-mock-mvc-example/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <groupId>io.rest-assured.examples</groupId>
         <artifactId>example-parent</artifactId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -77,13 +77,13 @@
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>spring-mock-mvc</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>scala-support</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/examples/scalatra-example/pom.xml b/examples/scalatra-example/pom.xml
index 7dfdde45..bd0cb68a 100644
--- a/examples/scalatra-example/pom.xml
+++ b/examples/scalatra-example/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <groupId>io.rest-assured.examples</groupId>
         <artifactId>example-parent</artifactId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/examples/scalatra-webapp/pom.xml b/examples/scalatra-webapp/pom.xml
index d555b5ad..0a0e4bfe 100644
--- a/examples/scalatra-webapp/pom.xml
+++ b/examples/scalatra-webapp/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <groupId>io.rest-assured.examples</groupId>
         <artifactId>example-parent</artifactId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -38,7 +38,7 @@
         <dependency>
             <groupId>io.rest-assured.examples</groupId>
             <artifactId>scalatra-example</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>javax.servlet</groupId>
diff --git a/examples/spring-mvc-webapp/pom.xml b/examples/spring-mvc-webapp/pom.xml
index 822fa37b..fc827d9d 100644
--- a/examples/spring-mvc-webapp/pom.xml
+++ b/examples/spring-mvc-webapp/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <groupId>io.rest-assured.examples</groupId>
         <artifactId>example-parent</artifactId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -54,7 +54,7 @@
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>spring-mock-mvc</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/json-path/pom.xml b/json-path/pom.xml
index 593a8964..c901230e 100644
--- a/json-path/pom.xml
+++ b/json-path/pom.xml
@@ -20,12 +20,12 @@
     <parent>
         <groupId>io.rest-assured</groupId>
         <artifactId>rest-assured-parent</artifactId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <groupId>io.rest-assured</groupId>
     <artifactId>json-path</artifactId>
     <packaging>bundle</packaging>
-    <version>5.0.1-SNAPSHOT</version>
+    <version>5.1.1-SNAPSHOT</version>
     <name>json-path</name>
     <url>http://maven.apache.org</url>
     <properties>
@@ -49,6 +49,7 @@
                         	io.restassured.internal.path.json.*
                        	</Export-Package>
                         <Import-Package>
+                            groovy.*;version="${groovy.range}",
                             org.apache.groovy.*;version="${groovy.range}",
                             *
 						</Import-Package>
@@ -115,7 +116,7 @@
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>rest-assured-common</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
         </dependency>
 
         <!-- Test -->
diff --git a/json-path/src/main/groovy/io/restassured/internal/path/json/ConfigurableJsonSlurper.groovy b/json-path/src/main/groovy/io/restassured/internal/path/json/ConfigurableJsonSlurper.groovy
index 5e88ffab..3d0788a7 100644
--- a/json-path/src/main/groovy/io/restassured/internal/path/json/ConfigurableJsonSlurper.groovy
+++ b/json-path/src/main/groovy/io/restassured/internal/path/json/ConfigurableJsonSlurper.groovy
@@ -43,265 +43,265 @@ import static groovy.json.JsonTokenType.*
  *
  */
 class ConfigurableJsonSlurper {
-    private static ThreadLocal<NumberReturnType> numberReturnType = new ThreadLocal<>();
-
-    static {
-        def original = JsonToken.metaClass.getMetaMethod("getValue")
-        JsonToken.metaClass.getValue = {->
-            def result = original.invoke(delegate)
-          NumberReturnType numberReturnType = numberReturnType.get()
-          if (numberReturnType.isFloatOrDouble() && result instanceof BigDecimal) {
-              // Convert big decimal to float or double
-              if (result > Float.MAX_VALUE || numberReturnType == NumberReturnType.DOUBLE) {
-                  result = result.doubleValue();
-              } else {
-                  result = result.floatValue();
-              }
-          } else if (NumberReturnType.BIG_INTEGER.equals(numberReturnType)
-                        && (result instanceof Integer || result instanceof Long)) {
-              result = new BigInteger(result.toString());
-          }
-          result
+  private static ThreadLocal<NumberReturnType> numberReturnType = new ThreadLocal<>()
+
+  static {
+    def original = JsonToken.metaClass.getMetaMethod("getValue")
+    JsonToken.metaClass.getValue = { ->
+      def result = original.invoke(delegate)
+      NumberReturnType numberReturnType = numberReturnType.get()
+      if (numberReturnType.isFloatOrDouble() && result instanceof BigDecimal) {
+        // Convert big decimal to float or double
+        if (result > Float.MAX_VALUE || numberReturnType == NumberReturnType.DOUBLE) {
+          result = result.doubleValue()
+        } else {
+          result = result.floatValue()
         }
+      } else if (NumberReturnType.BIG_INTEGER.equals(numberReturnType)
+              && (result instanceof Integer || result instanceof Long)) {
+        result = new BigInteger(result.toString())
+      }
+      result
     }
-
-    public ConfigurableJsonSlurper(NumberReturnType numberReturnType) {
-        ConfigurableJsonSlurper.numberReturnType.set(numberReturnType);
+  }
+
+  ConfigurableJsonSlurper(NumberReturnType numberReturnType) {
+    ConfigurableJsonSlurper.numberReturnType.set(numberReturnType)
+  }
+
+  /**
+   * Parse a text representation of a JSON data structure
+   *
+   * @param text JSON text to parse
+   * @return a data structure of lists and maps
+   */
+  Object parseText(String text) {
+    if (text == null || text.length() == 0) {
+      throw new IllegalArgumentException("The JSON input text should neither be null nor empty.")
     }
 
-    /**
-     * Parse a text representation of a JSON data structure
-     *
-     * @param text JSON text to parse
-     * @return a data structure of lists and maps
-     */
-    public Object parseText(String text) {
-        if (text == null || text.length() == 0) {
-            throw new IllegalArgumentException("The JSON input text should neither be null nor empty.");
-        }
-
-        return parse(new LineColumnReader(new StringReader(text)));
+    return parse(new LineColumnReader(new StringReader(text)))
+  }
+
+  /**
+   * Parse a JSON data structure from content from a reader
+   *
+   * @param reader reader over a JSON content
+   * @return a data structure of lists and maps
+   */
+  Object parse(Reader reader) {
+    Object content
+
+    JsonLexer lexer = new JsonLexer(reader)
+
+    JsonToken token = lexer.nextToken()
+    // Added by Johan Haleby
+    // We need to check if the token is null (this happens if the document to parse is empty, see issue 260).
+    if (token == null) {
+      return null
     }
-
-    /**
-     * Parse a JSON data structure from content from a reader
-     *
-     * @param reader reader over a JSON content
-     * @return a data structure of lists and maps
-     */
-    public Object parse(Reader reader) {
-        Object content;
-
-        JsonLexer lexer = new JsonLexer(reader);
-
-        JsonToken token = lexer.nextToken();
-        // Added by Johan Haleby
-        // We need to check if the token is null (this happens if the document to parse is empty, see issue 260).
-        if (token == null) {
-            return null;
-        }
-        // End add
-        if (token.getType() == OPEN_CURLY) {
-            content = parseObject(lexer);
-        } else if (token.getType() == OPEN_BRACKET) {
-            content = parseArray(lexer);
-        } else {
-            throw new JsonException(
-                    "A JSON payload should start with " + OPEN_CURLY.getLabel() +
-                            " or " + OPEN_BRACKET.getLabel() + ".\n" +
-                            "Instead, '" + token.getText() + "' was found " +
-                            "on line: " + token.getStartLine() + ", " +
-                            "column: " + token.getStartColumn()
-            );
-        }
-
-        return content;
+    // End add
+    if (token.getType() == OPEN_CURLY) {
+      content = parseObject(lexer)
+    } else if (token.getType() == OPEN_BRACKET) {
+      content = parseArray(lexer)
+    } else {
+      throw new JsonException(
+              "A JSON payload should start with " + OPEN_CURLY.getLabel() +
+                      " or " + OPEN_BRACKET.getLabel() + ".\n" +
+                      "Instead, '" + token.getText() + "' was found " +
+                      "on line: " + token.getStartLine() + ", " +
+                      "column: " + token.getStartColumn()
+      )
     }
 
-    /**
-     * Parse an array from the lexer
-     *
-     * @param lexer the lexer
-     * @return a list of JSON values
-     */
-    private List parseArray(JsonLexer lexer) {
-        List content = new ArrayList();
-
-        JsonToken currentToken;
-
-        for (; ;) {
-            currentToken = lexer.nextToken();
-
-            if (currentToken == null) {
-                throw new JsonException(
-                        "Expected a value on line: " + lexer.getReader().getLine() + ", " +
-                                "column: " + lexer.getReader().getColumn() + ".\n" +
-                                "But got an unterminated array."
-                );
-            }
-
-            if (currentToken.getType() == OPEN_CURLY) {
-                content.add(parseObject(lexer));
-            } else if (currentToken.getType() == OPEN_BRACKET) {
-                content.add(parseArray(lexer));
-            } else if (currentToken.getType().ordinal() >= NULL.ordinal()) {
-                content.add(currentToken.getValue());
-            } else if (currentToken.getType() == CLOSE_BRACKET) {
-                return content;
-            } else {
-                throw new JsonException(
-                        "Expected a value, an array, or an object " +
-                                "on line: " + currentToken.getStartLine() + ", " +
-                                "column: " + currentToken.getStartColumn() + ".\n" +
-                                "But got '" + currentToken.getText() + "' instead."
-                );
-            }
-
-            currentToken = lexer.nextToken();
-
-            if (currentToken == null) {
-                throw new JsonException(
-                        "Expected " + CLOSE_BRACKET.getLabel() + " " +
-                                "or " + COMMA.getLabel() + " " +
-                                "on line: " + lexer.getReader().getLine() + ", " +
-                                "column: " + lexer.getReader().getColumn() + ".\n" +
-                                "But got an unterminated array."
-                );
-            }
-
-            // Expect a comma for an upcoming value
-            // or a closing bracket for the end of the array
-            if (currentToken.getType() == CLOSE_BRACKET) {
-                break;
-            } else if (currentToken.getType() != COMMA) {
-                throw new JsonException(
-                        "Expected a value or " + CLOSE_BRACKET.getLabel() + " " +
-                                "on line: " + currentToken.getStartLine() + " " +
-                                "column: " + currentToken.getStartColumn() + ".\n" +
-                                "But got '" + currentToken.getText() + "' instead."
-                );
-            }
-        }
-
-        return content;
+    return content
+  }
+
+  /**
+   * Parse an array from the lexer
+   *
+   * @param lexer the lexer
+   * @return a list of JSON values
+   */
+  private List parseArray(JsonLexer lexer) {
+    List content = new ArrayList()
+
+    JsonToken currentToken
+
+    for (; ;) {
+      currentToken = lexer.nextToken()
+
+      if (currentToken == null) {
+        throw new JsonException(
+                "Expected a value on line: " + lexer.getReader().getLine() + ", " +
+                        "column: " + lexer.getReader().getColumn() + ".\n" +
+                        "But got an unterminated array."
+        )
+      }
+
+      if (currentToken.getType() == OPEN_CURLY) {
+        content.add(parseObject(lexer))
+      } else if (currentToken.getType() == OPEN_BRACKET) {
+        content.add(parseArray(lexer))
+      } else if (currentToken.getType().ordinal() >= NULL.ordinal()) {
+        content.add(currentToken.getValue())
+      } else if (currentToken.getType() == CLOSE_BRACKET) {
+        return content
+      } else {
+        throw new JsonException(
+                "Expected a value, an array, or an object " +
+                        "on line: " + currentToken.getStartLine() + ", " +
+                        "column: " + currentToken.getStartColumn() + ".\n" +
+                        "But got '" + currentToken.getText() + "' instead."
+        )
+      }
+
+      currentToken = lexer.nextToken()
+
+      if (currentToken == null) {
+        throw new JsonException(
+                "Expected " + CLOSE_BRACKET.getLabel() + " " +
+                        "or " + COMMA.getLabel() + " " +
+                        "on line: " + lexer.getReader().getLine() + ", " +
+                        "column: " + lexer.getReader().getColumn() + ".\n" +
+                        "But got an unterminated array."
+        )
+      }
+
+      // Expect a comma for an upcoming value
+      // or a closing bracket for the end of the array
+      if (currentToken.getType() == CLOSE_BRACKET) {
+        break
+      } else if (currentToken.getType() != COMMA) {
+        throw new JsonException(
+                "Expected a value or " + CLOSE_BRACKET.getLabel() + " " +
+                        "on line: " + currentToken.getStartLine() + " " +
+                        "column: " + currentToken.getStartColumn() + ".\n" +
+                        "But got '" + currentToken.getText() + "' instead."
+        )
+      }
     }
 
-    /**
-     * Parses an object from the lexer
-     *
-     * @param lexer the lexer
-     * @return a Map representing a JSON object
-     */
-    private Map parseObject(JsonLexer lexer) {
-        Map content = new LinkedHashMap();
-
-        JsonToken previousToken = null;
-        JsonToken currentToken = null;
-
-        for (; ;) {
-            currentToken = lexer.nextToken();
-
-            if (currentToken == null) {
-                throw new JsonException(
-                        "Expected a String key on line: " + lexer.getReader().getLine() + ", " +
-                                "column: " + lexer.getReader().getColumn() + ".\n" +
-                                "But got an unterminated object."
-                );
-            }
-
-            // expect a string key, or already a closing curly brace
-
-            if (currentToken.getType() == CLOSE_CURLY) {
-                return content;
-            } else if (currentToken.getType() != STRING) {
-                throw new JsonException(
-                        "Expected " + STRING.getLabel() + " key " +
-                                "on line: " + currentToken.getStartLine() + ", " +
-                                "column: " + currentToken.getStartColumn() + ".\n" +
-                                "But got '" + currentToken.getText() + "' instead."
-                );
-            }
-
-            String mapKey = (String) currentToken.getValue();
-
-            currentToken = lexer.nextToken();
-
-            if (currentToken == null) {
-                throw new JsonException(
-                        "Expected a " + COLON.getLabel() + " " +
-                                "on line: " + lexer.getReader().getLine() + ", " +
-                                "column: " + lexer.getReader().getColumn() + ".\n" +
-                                "But got an unterminated object."
-                );
-            }
-
-            // expect a colon between the key and value pair
-
-            if (currentToken.getType() != COLON) {
-                throw new JsonException(
-                        "Expected " + COLON.getLabel() + " " +
-                                "on line: " + currentToken.getStartLine() + ", " +
-                                "column: " + currentToken.getStartColumn() + ".\n" +
-                                "But got '" + currentToken.getText() + "' instead."
-                );
-            }
-
-            currentToken = lexer.nextToken();
-
-            if (currentToken == null) {
-                throw new JsonException(
-                        "Expected a value " +
-                                "on line: " + lexer.getReader().getLine() + ", " +
-                                "column: " + lexer.getReader().getColumn() + ".\n" +
-                                "But got an unterminated object."
-                );
-            }
-
-            // value can be an object, an array, a number, string, boolean or null values
-
-            if (currentToken.getType() == OPEN_CURLY) {
-                content.put(mapKey, parseObject(lexer));
-            } else if (currentToken.getType() == OPEN_BRACKET) {
-                content.put(mapKey, parseArray(lexer));
-            } else if (currentToken.getType().ordinal() >= NULL.ordinal()) {
-                content.put(mapKey, currentToken.getValue());
-            } else {
-                throw new JsonException(
-                        "Expected a value, an array, or an object " +
-                                "on line: " + currentToken.getStartLine() + ", " +
-                                "column: " + currentToken.getStartColumn() + ".\n" +
-                                "But got '" + currentToken.getText() + "' instead."
-                );
-            }
-
-            previousToken = currentToken;
-            currentToken = lexer.nextToken();
-
-            // premature end of the object
-
-            if (currentToken == null) {
-                throw new JsonException(
-                        "Expected " + CLOSE_CURLY.getLabel() + " or " + COMMA.getLabel() + " " +
-                                "on line: " + previousToken.getEndLine() + ", " +
-                                "column: " + previousToken.getEndColumn() + ".\n" +
-                                "But got an unterminated object."
-                );
-            }
-
-            // Expect a comma for an upcoming key/value pair
-            // or a closing curly brace for the end of the object
-            if (currentToken.getType() == CLOSE_CURLY) {
-                break;
-            } else if (currentToken.getType() != COMMA) {
-                throw new JsonException(
-                        "Expected a value or " + CLOSE_CURLY.getLabel() + " " +
-                                "on line: " + currentToken.getStartLine() + ", " +
-                                "column: " + currentToken.getStartColumn() + ".\n" +
-                                "But got '" + currentToken.getText() + "' instead."
-                );
-            }
-        }
-
-        return content;
+    return content
+  }
+
+  /**
+   * Parses an object from the lexer
+   *
+   * @param lexer the lexer
+   * @return a Map representing a JSON object
+   */
+  private Map parseObject(JsonLexer lexer) {
+    Map content = new LinkedHashMap()
+
+    JsonToken previousToken = null
+    JsonToken currentToken = null
+
+    for (; ;) {
+      currentToken = lexer.nextToken()
+
+      if (currentToken == null) {
+        throw new JsonException(
+                "Expected a String key on line: " + lexer.getReader().getLine() + ", " +
+                        "column: " + lexer.getReader().getColumn() + ".\n" +
+                        "But got an unterminated object."
+        )
+      }
+
+      // expect a string key, or already a closing curly brace
+
+      if (currentToken.getType() == CLOSE_CURLY) {
+        return content
+      } else if (currentToken.getType() != STRING) {
+        throw new JsonException(
+                "Expected " + STRING.getLabel() + " key " +
+                        "on line: " + currentToken.getStartLine() + ", " +
+                        "column: " + currentToken.getStartColumn() + ".\n" +
+                        "But got '" + currentToken.getText() + "' instead."
+        )
+      }
+
+      String mapKey = (String) currentToken.getValue()
+
+      currentToken = lexer.nextToken()
+
+      if (currentToken == null) {
+        throw new JsonException(
+                "Expected a " + COLON.getLabel() + " " +
+                        "on line: " + lexer.getReader().getLine() + ", " +
+                        "column: " + lexer.getReader().getColumn() + ".\n" +
+                        "But got an unterminated object."
+        )
+      }
+
+      // expect a colon between the key and value pair
+
+      if (currentToken.getType() != COLON) {
+        throw new JsonException(
+                "Expected " + COLON.getLabel() + " " +
+                        "on line: " + currentToken.getStartLine() + ", " +
+                        "column: " + currentToken.getStartColumn() + ".\n" +
+                        "But got '" + currentToken.getText() + "' instead."
+        )
+      }
+
+      currentToken = lexer.nextToken()
+
+      if (currentToken == null) {
+        throw new JsonException(
+                "Expected a value " +
+                        "on line: " + lexer.getReader().getLine() + ", " +
+                        "column: " + lexer.getReader().getColumn() + ".\n" +
+                        "But got an unterminated object."
+        )
+      }
+
+      // value can be an object, an array, a number, string, boolean or null values
+
+      if (currentToken.getType() == OPEN_CURLY) {
+        content.put(mapKey, parseObject(lexer))
+      } else if (currentToken.getType() == OPEN_BRACKET) {
+        content.put(mapKey, parseArray(lexer))
+      } else if (currentToken.getType().ordinal() >= NULL.ordinal()) {
+        content.put(mapKey, currentToken.getValue())
+      } else {
+        throw new JsonException(
+                "Expected a value, an array, or an object " +
+                        "on line: " + currentToken.getStartLine() + ", " +
+                        "column: " + currentToken.getStartColumn() + ".\n" +
+                        "But got '" + currentToken.getText() + "' instead."
+        )
+      }
+
+      previousToken = currentToken
+      currentToken = lexer.nextToken()
+
+      // premature end of the object
+
+      if (currentToken == null) {
+        throw new JsonException(
+                "Expected " + CLOSE_CURLY.getLabel() + " or " + COMMA.getLabel() + " " +
+                        "on line: " + previousToken.getEndLine() + ", " +
+                        "column: " + previousToken.getEndColumn() + ".\n" +
+                        "But got an unterminated object."
+        )
+      }
+
+      // Expect a comma for an upcoming key/value pair
+      // or a closing curly brace for the end of the object
+      if (currentToken.getType() == CLOSE_CURLY) {
+        break
+      } else if (currentToken.getType() != COMMA) {
+        throw new JsonException(
+                "Expected a value or " + CLOSE_CURLY.getLabel() + " " +
+                        "on line: " + currentToken.getStartLine() + ", " +
+                        "column: " + currentToken.getStartColumn() + ".\n" +
+                        "But got '" + currentToken.getText() + "' instead."
+        )
+      }
     }
+
+    return content
+  }
 }
diff --git a/json-path/src/main/groovy/io/restassured/internal/path/json/JSONAssertion.groovy b/json-path/src/main/groovy/io/restassured/internal/path/json/JSONAssertion.groovy
index 581cc7dd..23c4b1ed 100644
--- a/json-path/src/main/groovy/io/restassured/internal/path/json/JSONAssertion.groovy
+++ b/json-path/src/main/groovy/io/restassured/internal/path/json/JSONAssertion.groovy
@@ -21,23 +21,23 @@ import io.restassured.internal.common.assertion.Assertion
 import static io.restassured.internal.common.assertion.AssertionSupport.*
 
 class JSONAssertion implements Assertion {
-  String key;
-  Map<String, Object> params;
+  String key
+  Map<String, Object> params
 
-  def Object getResult(object, config) {
+  Object getResult(object, config) {
     Object result = getAsJsonObject(object)
-    return result;
+    return result
   }
 
   def getAsJsonObject(object) {
-    key = escapePath(key, hyphen(), attributeGetter(), integer(), properties(), classKeyword());
-    def result;
+    key = escapePath(key, hyphen(), attributeGetter(), integer(), properties(), classKeyword())
+    def result
     if (key == "\$" || key == "") {
       result = object
     } else {
       def root = 'restAssuredJsonRootObject'
       try {
-        def expr;
+        def expr
         if (key =~ /^\[\d+\].*/) {
           expr = "$root$key"
         } else {
@@ -46,36 +46,40 @@ class JSONAssertion implements Assertion {
         result = eval(root, object, expr)
       } catch (MissingPropertyException e) {
         // This means that a param was used that was not defined
-        String error = String.format("The parameter \"%s\" was used but not defined. Define parameters using the JsonPath.params(...) function", e.property);
-        throw new IllegalArgumentException(error, e);
+        String error = String.format("The parameter \"%s\" was used but not defined. Define parameters using the JsonPath.params(...) function", e.property)
+        throw new IllegalArgumentException(error, e)
       } catch (Exception e) {
         // Check if exception is due to a missing property
-        if (e instanceof NullPointerException && e.getMessage().startsWith("Cannot get property") && e.getMessage().endsWith("on null object")) {
-          return null
+        if (e instanceof NullPointerException){
+          def message = e.getMessage();
+          if (message.equals("Cannot invoke method getAt() on null object") ||
+             (message.startsWith("Cannot get property") && message.endsWith("on null object"))) {
+            return null
+          }
         }
-        String error = e.getMessage().replace("startup failed:", "Invalid JSON expression:").replace("$root.", generateWhitespace(root.length()));
-        throw new IllegalArgumentException(error, e);
+        String error = e.getMessage().replace("startup failed:", "Invalid JSON expression:").replace("$root.", generateWhitespace(root.length()))
+        throw new IllegalArgumentException(error, e)
       }
     }
     return result
   }
 
-  def String description() {
+  String description() {
     return "JSON path"
   }
 
   private def eval(root, object, expr) {
-    Map<String, Object> newParams;
+    Map<String, Object> newParams
     // Create parameters from given ones
     if (params != null) {
-      newParams = new HashMap<>(params);
+      newParams = new HashMap<>(params)
     } else {
-      newParams = new HashMap<>();
+      newParams = new HashMap<>()
     }
     // Add object to evaluate
-    newParams.put(root, object);
+    newParams.put(root, object)
     // Create shell with variables set
-    GroovyShell sh = new GroovyShell(new Binding(newParams));
+    GroovyShell sh = new GroovyShell(new Binding(newParams))
     // Run
     def res = sh.evaluate(expr)
     sh.resetLoadedClasses()
diff --git a/json-path/src/main/groovy/io/restassured/internal/path/json/JsonPrettifier.groovy b/json-path/src/main/groovy/io/restassured/internal/path/json/JsonPrettifier.groovy
deleted file mode 100644
index cb9e34ba..00000000
--- a/json-path/src/main/groovy/io/restassured/internal/path/json/JsonPrettifier.groovy
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2019 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.restassured.internal.path.json
-
-import groovy.json.JsonOutput
-import org.apache.commons.lang3.text.translate.UnicodeUnescaper
-
-class JsonPrettifier {
-
-  static def String prettifyJson(String json) {
-    def prettified = JsonOutput.prettyPrint(json)
-    new UnicodeUnescaper().translate(prettified)
-  }
-}
diff --git a/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonObjectDeserializer.groovy b/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonObjectDeserializer.groovy
deleted file mode 100644
index b23e7d67..00000000
--- a/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonObjectDeserializer.groovy
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright 2019,2020 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-
-package io.restassured.internal.path.json.mapping
-
-import io.restassured.common.mapper.DataToDeserialize
-import io.restassured.common.mapper.ObjectDeserializationContext
-import io.restassured.common.mapper.resolver.ObjectMapperResolver
-import io.restassured.internal.common.mapper.ObjectDeserializationContextImpl
-import io.restassured.path.json.config.JsonParserType
-import io.restassured.path.json.config.JsonPathConfig
-import io.restassured.path.json.mapper.factory.*
-import org.apache.commons.lang3.Validate
-
-class JsonObjectDeserializer {
-
-    public static <T> T deserialize(String json, Class<T> cls, JsonPathConfig jsonPathConfig) {
-        Validate.notNull(jsonPathConfig, "JsonPath configuration wasn't specified, cannot deserialize.")
-        def mapperType = jsonPathConfig.defaultParserType()
-        def deserializationCtx = new ObjectDeserializationContextImpl()
-        deserializationCtx.type = cls
-        deserializationCtx.charset = jsonPathConfig.charset()
-        deserializationCtx.dataToDeserialize = new DataToDeserialize() {
-
-            @Override
-            String asString() {
-                return json
-            }
-
-            @Override
-            byte[] asByteArray() {
-                return json.getBytes(jsonPathConfig.charset())
-            }
-
-            @Override
-            InputStream asInputStream() {
-                return new ByteArrayInputStream(asByteArray())
-            }
-        }
-
-        if (jsonPathConfig.hasDefaultDeserializer()) {
-            return jsonPathConfig.defaultDeserializer().deserialize(deserializationCtx) as T;
-        } else if (mapperType != null || jsonPathConfig.hasDefaultParserType()) {
-            JsonParserType mapperTypeToUse = mapperType == null ? jsonPathConfig.defaultParserType() : mapperType;
-            return deserializeWithObjectMapper(deserializationCtx, mapperTypeToUse, jsonPathConfig)
-        }
-
-        if (ObjectMapperResolver.isJackson2InClassPath()) {
-            return deserializeWithJackson2(deserializationCtx, jsonPathConfig.jackson2ObjectMapperFactory()) as T
-        } else if (ObjectMapperResolver.isJackson1InClassPath()) {
-            return deserializeWithJackson1(deserializationCtx, jsonPathConfig.jackson1ObjectMapperFactory()) as T
-        } else if (ObjectMapperResolver.isGsonInClassPath()) {
-            return deserializeWithGson(deserializationCtx, jsonPathConfig.gsonObjectMapperFactory()) as T
-        } else if (ObjectMapperResolver.isJohnzonInClassPath()) {
-            return deserializeWithJohnzon(deserializationCtx, jsonPathConfig.johnzonObjectMapperFactory()) as T
-        } else if (ObjectMapperResolver.isYassonInClassPath()) {
-            return deserializeWithJsonb(deserializationCtx, jsonPathConfig.jsonbObjectMapperFactory()) as T
-        }
-        throw new IllegalStateException("Cannot deserialize object because no JSON deserializer found in classpath. Please put Jackson (Databind), Gson, Jackson, or Yasson in the classpath.")
-    }
-
-    private static <T> T deserializeWithObjectMapper(ObjectDeserializationContext ctx, JsonParserType mapperType, JsonPathConfig config) {
-        if (mapperType == JsonParserType.JACKSON_2 && ObjectMapperResolver.isJackson2InClassPath()) {
-            return deserializeWithJackson2(ctx, config.jackson2ObjectMapperFactory()) as T
-        } else if (mapperType == JsonParserType.JACKSON_1 && ObjectMapperResolver.isJackson1InClassPath()) {
-            return deserializeWithJackson1(ctx, config.jackson1ObjectMapperFactory()) as T
-        } else if (mapperType == JsonParserType.GSON && ObjectMapperResolver.isGsonInClassPath()) {
-            return deserializeWithGson(ctx, config.gsonObjectMapperFactory()) as T
-        } else if (mapperType == JsonParserType.JOHNZON && ObjectMapperResolver.isJohnzonInClassPath()) {
-            return deserializeWithJohnzon(ctx, config.johnzonObjectMapperFactory()) as T
-        } else if (mapperType == JsonParserType.JSONB && ObjectMapperResolver.isYassonInClassPath()) {
-            return deserializeWithJsonb(ctx, config.jsonbObjectMapperFactory()) as T
-        } else {
-            def lowerCase = mapperType.toString().toLowerCase()
-            throw new IllegalArgumentException("Cannot deserialize object using $mapperType because $lowerCase doesn't exist in the classpath.")
-        }
-    }
-
-    private static def deserializeWithGson(ObjectDeserializationContext ctx, GsonObjectMapperFactory factory) {
-        new JsonPathGsonObjectDeserializer(factory).deserialize(ctx)
-    }
-
-    static def deserializeWithJackson1(ObjectDeserializationContext ctx, Jackson1ObjectMapperFactory factory) {
-        new JsonPathJackson1ObjectDeserializer(factory).deserialize(ctx)
-    }
-
-    static def deserializeWithJackson2(ObjectDeserializationContext ctx, Jackson2ObjectMapperFactory factory) {
-        new JsonPathJackson2ObjectDeserializer(factory).deserialize(ctx)
-    }
-
-	static def deserializeWithJohnzon(ObjectDeserializationContext ctx, JohnzonObjectMapperFactory factory) {
-		new JsonPathJohnzonObjectDeserializer(factory).deserialize(ctx)
-	}
-	
-	static def deserializeWithJsonb(ObjectDeserializationContext ctx, JsonbObjectMapperFactory factory) {
-        new JsonPathJsonbObjectDeserializer(factory).deserialize(ctx)
-    }
-}
diff --git a/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonPathGsonObjectDeserializer.groovy b/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonPathGsonObjectDeserializer.groovy
deleted file mode 100644
index 136a7812..00000000
--- a/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonPathGsonObjectDeserializer.groovy
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2019 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.restassured.internal.path.json.mapping
-
-import io.restassured.common.mapper.ObjectDeserializationContext
-import io.restassured.path.json.mapper.factory.GsonObjectMapperFactory
-import io.restassured.path.json.mapping.JsonPathObjectDeserializer
-
-import static io.restassured.internal.common.assertion.AssertParameter.notNull
-
-class JsonPathGsonObjectDeserializer implements JsonPathObjectDeserializer {
-  private final GsonObjectMapperFactory factory
-
-  JsonPathGsonObjectDeserializer(GsonObjectMapperFactory factory) {
-    notNull(factory, "GsonObjectMapperFactory")
-    this.factory = factory;
-  }
-
-  @Override
-  def deserialize(ObjectDeserializationContext ctx) {
-    factory.create(ctx.type, ctx.charset).fromJson(ctx.dataToDeserialize.asString(), ctx.type)
-  }
-}
diff --git a/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonPathJackson1ObjectDeserializer.groovy b/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonPathJackson1ObjectDeserializer.groovy
deleted file mode 100644
index 544b9fab..00000000
--- a/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonPathJackson1ObjectDeserializer.groovy
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2019 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package io.restassured.internal.path.json.mapping
-
-import io.restassured.common.mapper.ObjectDeserializationContext
-import io.restassured.path.json.mapper.factory.Jackson1ObjectMapperFactory
-import io.restassured.path.json.mapping.JsonPathObjectDeserializer
-import org.codehaus.jackson.type.JavaType
-
-import java.lang.reflect.Type
-
-class JsonPathJackson1ObjectDeserializer implements JsonPathObjectDeserializer {
-
-  private final Jackson1ObjectMapperFactory factory;
-
-  JsonPathJackson1ObjectDeserializer(Jackson1ObjectMapperFactory factory) {
-    this.factory = factory
-  }
-
-  private org.codehaus.jackson.map.ObjectMapper createJacksonObjectMapper(Type cls, String charset) {
-    return factory.create(cls, charset)
-  }
-
-  @Override
-  def deserialize(ObjectDeserializationContext ctx) {
-    def object = ctx.getDataToDeserialize().asString()
-    def cls = ctx.getType()
-    def mapper = createJacksonObjectMapper(cls, ctx.getCharset())
-    JavaType javaType = mapper.constructType(cls)
-    mapper.readValue(object, javaType)
-  }
-}
diff --git a/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonPathJackson2ObjectDeserializer.groovy b/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonPathJackson2ObjectDeserializer.groovy
deleted file mode 100644
index a0c6dbf0..00000000
--- a/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonPathJackson2ObjectDeserializer.groovy
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2019 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package io.restassured.internal.path.json.mapping
-
-import com.fasterxml.jackson.databind.JavaType
-import com.fasterxml.jackson.databind.ObjectMapper
-import io.restassured.common.mapper.ObjectDeserializationContext
-import io.restassured.path.json.mapper.factory.Jackson2ObjectMapperFactory
-import io.restassured.path.json.mapping.JsonPathObjectDeserializer
-
-import java.lang.reflect.Type
-
-class JsonPathJackson2ObjectDeserializer implements JsonPathObjectDeserializer {
-
-  private final Jackson2ObjectMapperFactory factory;
-
-  JsonPathJackson2ObjectDeserializer(Jackson2ObjectMapperFactory factory) {
-    this.factory = factory
-  }
-
-  private ObjectMapper createJackson2ObjectMapper(Type cls, String charset) {
-    return factory.create(cls, charset)
-  }
-
-  @Override
-  deserialize(ObjectDeserializationContext context) {
-    def object = context.getDataToDeserialize().asString()
-    def cls = context.getType()
-    def mapper = createJackson2ObjectMapper(cls, context.getCharset())
-    JavaType javaType = mapper.constructType(cls)
-    mapper.readValue(object, javaType)
-  }
-}
\ No newline at end of file
diff --git a/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonPathJohnzonObjectDeserializer.groovy b/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonPathJohnzonObjectDeserializer.groovy
deleted file mode 100644
index 43b0b1b8..00000000
--- a/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonPathJohnzonObjectDeserializer.groovy
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2019 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.restassured.internal.path.json.mapping
-
-import io.restassured.common.mapper.ObjectDeserializationContext
-import io.restassured.path.json.mapper.factory.JohnzonObjectMapperFactory
-import io.restassured.path.json.mapping.JsonPathObjectDeserializer
-import org.apache.johnzon.mapper.Mapper
-
-import java.lang.reflect.Type
-
-import static io.restassured.internal.common.assertion.AssertParameter.notNull
-
-class JsonPathJohnzonObjectDeserializer implements JsonPathObjectDeserializer {
-  private final JohnzonObjectMapperFactory factory
-
-  JsonPathJohnzonObjectDeserializer(JohnzonObjectMapperFactory factory) {
-    notNull(factory, "JohnzonObjectMapperFactory")
-    this.factory = factory;
-  }
-
-  private Mapper createJohnzonObjectMapper(Type cls, String charset) {
-    return factory.create(cls, charset)
-  }
-
-  @Override
-  def deserialize(ObjectDeserializationContext context) {
-    def cls = context.getType()
-    def mapper = createJohnzonObjectMapper(cls, context.getCharset())
-
-    context.getDataToDeserialize().asInputStream().withReader { reader ->
-      mapper.readObject(reader, cls)
-    }
-  }
-}
diff --git a/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonPathJsonbObjectDeserializer.groovy b/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonPathJsonbObjectDeserializer.groovy
deleted file mode 100644
index 22eae476..00000000
--- a/json-path/src/main/groovy/io/restassured/internal/path/json/mapping/JsonPathJsonbObjectDeserializer.groovy
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2020 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.restassured.internal.path.json.mapping
-
-import io.restassured.common.mapper.ObjectDeserializationContext
-import io.restassured.path.json.mapper.factory.JsonbObjectMapperFactory
-import io.restassured.path.json.mapping.JsonPathObjectDeserializer
-
-import javax.json.bind.Jsonb
-import java.lang.reflect.Type
-
-import static io.restassured.internal.common.assertion.AssertParameter.notNull
-
-class JsonPathJsonbObjectDeserializer implements JsonPathObjectDeserializer {
-  private final JsonbObjectMapperFactory factory
-
-  JsonPathJsonbObjectDeserializer(JsonbObjectMapperFactory factory) {
-    notNull(factory, "JsonbObjectMapperFactory")
-    this.factory = factory;
-  }
-
-  private Jsonb createJsonbObjectMapper(Type cls, String charset) {
-    return factory.create(cls, charset)
-  }
-
-  @Override
-  def deserialize(ObjectDeserializationContext context) {
-    def cls = context.getType()
-    def mapper = createJsonbObjectMapper(cls, context.getCharset())
-
-    context.getDataToDeserialize().asInputStream().withReader { reader ->
-      mapper.fromJson(reader, cls)
-    }
-  }
-}
diff --git a/json-path/src/main/java/io/restassured/internal/path/json/JsonPrettifier.java b/json-path/src/main/java/io/restassured/internal/path/json/JsonPrettifier.java
new file mode 100644
index 00000000..cbbdb65e
--- /dev/null
+++ b/json-path/src/main/java/io/restassured/internal/path/json/JsonPrettifier.java
@@ -0,0 +1,12 @@
+package io.restassured.internal.path.json;
+
+import groovy.json.JsonOutput;
+import org.apache.commons.lang3.text.translate.UnicodeUnescaper;
+
+public class JsonPrettifier {
+  public static String prettifyJson(String json) {
+    String prettified = JsonOutput.prettyPrint(json);
+    return new UnicodeUnescaper().translate(prettified);
+  }
+
+}
diff --git a/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonObjectDeserializer.java b/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonObjectDeserializer.java
new file mode 100644
index 00000000..0268430c
--- /dev/null
+++ b/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonObjectDeserializer.java
@@ -0,0 +1,103 @@
+package io.restassured.internal.path.json.mapping;
+
+import io.restassured.common.mapper.DataToDeserialize;
+import io.restassured.common.mapper.ObjectDeserializationContext;
+import io.restassured.common.mapper.resolver.ObjectMapperResolver;
+import io.restassured.internal.common.ObjectDeserializationContextImpl;
+import io.restassured.path.json.config.JsonParserType;
+import io.restassured.path.json.config.JsonPathConfig;
+import io.restassured.path.json.mapper.factory.*;
+import org.apache.commons.lang3.Validate;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+
+public class JsonObjectDeserializer {
+
+    @SuppressWarnings("unchecked")
+    public static <T> T deserialize(String json, Class<T> cls, JsonPathConfig jsonPathConfig) {
+        Validate.notNull(jsonPathConfig, "JsonPath configuration wasn't specified, cannot deserialize.");
+        JsonParserType mapperType = jsonPathConfig.defaultParserType();
+        DataToDeserialize dataToDeserialize = new DataToDeserialize() {
+
+            @Override
+            public String asString() {
+                return json;
+            }
+
+            @Override
+            public byte[] asByteArray() {
+                try {
+                    return json.getBytes(jsonPathConfig.charset());
+                } catch (UnsupportedEncodingException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
+            @Override
+            public InputStream asInputStream() {
+                return new ByteArrayInputStream(asByteArray());
+            }
+        };
+        ObjectDeserializationContext deserializationCtx = new ObjectDeserializationContextImpl(dataToDeserialize, cls, jsonPathConfig.charset());
+
+        if (jsonPathConfig.hasDefaultDeserializer()) {
+            return jsonPathConfig.defaultDeserializer().deserialize(deserializationCtx);
+        } else if (mapperType != null || jsonPathConfig.hasDefaultParserType()) {
+            JsonParserType mapperTypeToUse = mapperType == null ? jsonPathConfig.defaultParserType() : mapperType;
+            return deserializeWithObjectMapper(deserializationCtx, mapperTypeToUse, jsonPathConfig);
+        }
+
+        if (ObjectMapperResolver.isJackson2InClassPath()) {
+            return (T) deserializeWithJackson2(deserializationCtx, jsonPathConfig.jackson2ObjectMapperFactory());
+        } else if (ObjectMapperResolver.isJackson1InClassPath()) {
+            return (T) deserializeWithJackson1(deserializationCtx, jsonPathConfig.jackson1ObjectMapperFactory());
+        } else if (ObjectMapperResolver.isGsonInClassPath()) {
+            return (T) deserializeWithGson(deserializationCtx, jsonPathConfig.gsonObjectMapperFactory());
+        } else if (ObjectMapperResolver.isJohnzonInClassPath()) {
+            return (T) deserializeWithJohnzon(deserializationCtx, jsonPathConfig.johnzonObjectMapperFactory());
+        } else if (ObjectMapperResolver.isYassonInClassPath()) {
+            return (T) deserializeWithJsonb(deserializationCtx, jsonPathConfig.jsonbObjectMapperFactory());
+        }
+        throw new IllegalStateException("Cannot deserialize object because no JSON deserializer found in classpath. Please put Jackson (Databind), Gson, Jackson, or Yasson in the classpath.");
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> T deserializeWithObjectMapper(ObjectDeserializationContext ctx, JsonParserType mapperType, JsonPathConfig config) {
+        if (mapperType == JsonParserType.JACKSON_2 && ObjectMapperResolver.isJackson2InClassPath()) {
+            return (T) deserializeWithJackson2(ctx, config.jackson2ObjectMapperFactory());
+        } else if (mapperType == JsonParserType.JACKSON_1 && ObjectMapperResolver.isJackson1InClassPath()) {
+            return (T) deserializeWithJackson1(ctx, config.jackson1ObjectMapperFactory());
+        } else if (mapperType == JsonParserType.GSON && ObjectMapperResolver.isGsonInClassPath()) {
+            return (T) deserializeWithGson(ctx, config.gsonObjectMapperFactory());
+        } else if (mapperType == JsonParserType.JOHNZON && ObjectMapperResolver.isJohnzonInClassPath()) {
+            return (T) deserializeWithJohnzon(ctx, config.johnzonObjectMapperFactory());
+        } else if (mapperType == JsonParserType.JSONB && ObjectMapperResolver.isYassonInClassPath()) {
+            return (T) deserializeWithJsonb(ctx, config.jsonbObjectMapperFactory());
+        } else {
+            String lowerCase = mapperType.toString().toLowerCase();
+            throw new IllegalArgumentException(String.format("Cannot deserialize object using %s because %s doesn't exist in the classpath.", mapperType, lowerCase));
+        }
+    }
+
+    private static Object deserializeWithGson(ObjectDeserializationContext ctx, GsonObjectMapperFactory factory) {
+        return new JsonPathGsonObjectDeserializer(factory).deserialize(ctx);
+    }
+
+    static Object deserializeWithJackson1(ObjectDeserializationContext ctx, Jackson1ObjectMapperFactory factory) {
+        return new JsonPathJackson1ObjectDeserializer(factory).deserialize(ctx);
+    }
+
+    static Object deserializeWithJackson2(ObjectDeserializationContext ctx, Jackson2ObjectMapperFactory factory) {
+        return new JsonPathJackson2ObjectDeserializer(factory).deserialize(ctx);
+    }
+
+    static Object deserializeWithJohnzon(ObjectDeserializationContext ctx, JohnzonObjectMapperFactory factory) {
+        return new JsonPathJohnzonObjectDeserializer(factory).deserialize(ctx);
+    }
+
+    static Object deserializeWithJsonb(ObjectDeserializationContext ctx, JsonbObjectMapperFactory factory) {
+        return new JsonPathJsonbObjectDeserializer(factory).deserialize(ctx);
+    }
+}
diff --git a/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonPathGsonObjectDeserializer.java b/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonPathGsonObjectDeserializer.java
new file mode 100644
index 00000000..703acd1f
--- /dev/null
+++ b/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonPathGsonObjectDeserializer.java
@@ -0,0 +1,20 @@
+package io.restassured.internal.path.json.mapping;
+
+import io.restassured.common.mapper.ObjectDeserializationContext;
+import io.restassured.internal.common.assertion.AssertParameter;
+import io.restassured.path.json.mapper.factory.GsonObjectMapperFactory;
+import io.restassured.path.json.mapping.JsonPathObjectDeserializer;
+
+public class JsonPathGsonObjectDeserializer implements JsonPathObjectDeserializer {
+    public JsonPathGsonObjectDeserializer(GsonObjectMapperFactory factory) {
+        AssertParameter.notNull(factory, "GsonObjectMapperFactory");
+        this.factory = factory;
+    }
+
+    @Override
+    public <T> T deserialize(ObjectDeserializationContext ctx) {
+        return factory.create(ctx.getType(), ctx.getCharset()).fromJson(ctx.getDataToDeserialize().asString(), ctx.getType());
+    }
+
+    private final GsonObjectMapperFactory factory;
+}
diff --git a/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonPathJackson1ObjectDeserializer.java b/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonPathJackson1ObjectDeserializer.java
new file mode 100644
index 00000000..daa1f967
--- /dev/null
+++ b/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonPathJackson1ObjectDeserializer.java
@@ -0,0 +1,36 @@
+package io.restassured.internal.path.json.mapping;
+
+import io.restassured.common.mapper.ObjectDeserializationContext;
+import io.restassured.path.json.mapper.factory.Jackson1ObjectMapperFactory;
+import io.restassured.path.json.mapping.JsonPathObjectDeserializer;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.JavaType;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.lang.reflect.Type;
+
+public class JsonPathJackson1ObjectDeserializer implements JsonPathObjectDeserializer {
+    public JsonPathJackson1ObjectDeserializer(Jackson1ObjectMapperFactory factory) {
+        this.factory = factory;
+    }
+
+    private ObjectMapper createJacksonObjectMapper(Type cls, String charset) {
+        return factory.create(cls, charset);
+    }
+
+    @Override
+    public <T> T deserialize(ObjectDeserializationContext ctx) {
+        String object = ctx.getDataToDeserialize().asString();
+        Type cls = ctx.getType();
+        ObjectMapper mapper = createJacksonObjectMapper(cls, ctx.getCharset());
+        JavaType javaType = mapper.constructType(cls);
+        try {
+            return mapper.readValue(object, javaType);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private final Jackson1ObjectMapperFactory factory;
+}
diff --git a/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonPathJackson2ObjectDeserializer.java b/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonPathJackson2ObjectDeserializer.java
new file mode 100644
index 00000000..5d91f74c
--- /dev/null
+++ b/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonPathJackson2ObjectDeserializer.java
@@ -0,0 +1,35 @@
+package io.restassured.internal.path.json.mapping;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.restassured.common.mapper.ObjectDeserializationContext;
+import io.restassured.path.json.mapper.factory.Jackson2ObjectMapperFactory;
+import io.restassured.path.json.mapping.JsonPathObjectDeserializer;
+
+import java.lang.reflect.Type;
+
+public class JsonPathJackson2ObjectDeserializer implements JsonPathObjectDeserializer {
+    public JsonPathJackson2ObjectDeserializer(Jackson2ObjectMapperFactory factory) {
+        this.factory = factory;
+    }
+
+    private ObjectMapper createJackson2ObjectMapper(Type cls, String charset) {
+        return factory.create(cls, charset);
+    }
+
+    @Override
+    public <T> T deserialize(ObjectDeserializationContext context) {
+        String object = context.getDataToDeserialize().asString();
+        Type cls = context.getType();
+        ObjectMapper mapper = createJackson2ObjectMapper(cls, context.getCharset());
+        JavaType javaType = mapper.constructType(cls);
+        try {
+            return mapper.readValue(object, javaType);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private final Jackson2ObjectMapperFactory factory;
+}
diff --git a/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonPathJohnzonObjectDeserializer.java b/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonPathJohnzonObjectDeserializer.java
new file mode 100644
index 00000000..6f3ebdee
--- /dev/null
+++ b/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonPathJohnzonObjectDeserializer.java
@@ -0,0 +1,38 @@
+package io.restassured.internal.path.json.mapping;
+
+import groovy.lang.Closure;
+import io.restassured.common.mapper.ObjectDeserializationContext;
+import io.restassured.internal.common.assertion.AssertParameter;
+import io.restassured.path.json.mapper.factory.JohnzonObjectMapperFactory;
+import io.restassured.path.json.mapping.JsonPathObjectDeserializer;
+import org.apache.johnzon.mapper.Mapper;
+import org.codehaus.groovy.runtime.IOGroovyMethods;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.UncheckedIOException;
+import java.lang.reflect.Type;
+
+public class JsonPathJohnzonObjectDeserializer implements JsonPathObjectDeserializer {
+    public JsonPathJohnzonObjectDeserializer(JohnzonObjectMapperFactory factory) {
+        AssertParameter.notNull(factory, "JohnzonObjectMapperFactory");
+        this.factory = factory;
+    }
+
+    @Override
+    public <T> T deserialize(ObjectDeserializationContext context) {
+        final Type cls = context.getType();
+        try (Mapper mapper = factory.create(cls, context.getCharset())) {
+            return IOGroovyMethods.withReader(context.getDataToDeserialize().asInputStream(), new Closure<T>(this, this) {
+                public Object doCall(Object reader) {
+                    return mapper.readObject((Reader) reader, cls);
+                }
+
+            });
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private final JohnzonObjectMapperFactory factory;
+}
diff --git a/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonPathJsonbObjectDeserializer.java b/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonPathJsonbObjectDeserializer.java
new file mode 100644
index 00000000..90f21bf4
--- /dev/null
+++ b/json-path/src/main/java/io/restassured/internal/path/json/mapping/JsonPathJsonbObjectDeserializer.java
@@ -0,0 +1,36 @@
+package io.restassured.internal.path.json.mapping;
+
+import groovy.lang.Closure;
+import io.restassured.common.mapper.ObjectDeserializationContext;
+import io.restassured.internal.common.assertion.AssertParameter;
+import io.restassured.path.json.mapper.factory.JsonbObjectMapperFactory;
+import io.restassured.path.json.mapping.JsonPathObjectDeserializer;
+import org.codehaus.groovy.runtime.IOGroovyMethods;
+
+import javax.json.bind.Jsonb;
+import java.io.Reader;
+import java.lang.reflect.Type;
+
+public class JsonPathJsonbObjectDeserializer implements JsonPathObjectDeserializer {
+    public JsonPathJsonbObjectDeserializer(JsonbObjectMapperFactory factory) {
+        AssertParameter.notNull(factory, "JsonbObjectMapperFactory");
+        this.factory = factory;
+    }
+
+    @Override
+    public <T> T deserialize(ObjectDeserializationContext context) {
+        final Type cls = context.getType();
+        try (Jsonb mapper = factory.create(cls, context.getCharset())) {
+            return IOGroovyMethods.withReader(context.getDataToDeserialize().asInputStream(), new Closure<T>(this, this) {
+                public Object doCall(Object reader) {
+                    return mapper.fromJson((Reader) reader, cls);
+                }
+
+            });
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private final JsonbObjectMapperFactory factory;
+}
diff --git a/json-path/src/main/java/io/restassured/path/json/JsonPath.java b/json-path/src/main/java/io/restassured/path/json/JsonPath.java
index 65ea0209..c4ef2b6b 100644
--- a/json-path/src/main/java/io/restassured/path/json/JsonPath.java
+++ b/json-path/src/main/java/io/restassured/path/json/JsonPath.java
@@ -1092,4 +1092,4 @@ public class JsonPath {
         //noinspection unchecked
         return JsonObjectDeserializer.deserialize(object, objectType, cfg);
     }
-}
+}
\ No newline at end of file
diff --git a/modules/json-schema-validator/pom.xml b/modules/json-schema-validator/pom.xml
index 07da3ae8..0c735b49 100644
--- a/modules/json-schema-validator/pom.xml
+++ b/modules/json-schema-validator/pom.xml
@@ -20,10 +20,10 @@
     <parent>
         <groupId>io.rest-assured</groupId>
         <artifactId>modules</artifactId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <artifactId>json-schema-validator</artifactId>
-    <version>5.0.1-SNAPSHOT</version>
+    <version>5.1.1-SNAPSHOT</version>
     <name>json-schema-validator</name>
     <url>http://maven.apache.org</url>
     <properties>
diff --git a/modules/kotlin-extensions/pom.xml b/modules/kotlin-extensions/pom.xml
index 29c4bd32..220ccca5 100644
--- a/modules/kotlin-extensions/pom.xml
+++ b/modules/kotlin-extensions/pom.xml
@@ -20,10 +20,10 @@
     <parent>
         <groupId>io.rest-assured</groupId>
         <artifactId>modules</artifactId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <artifactId>kotlin-extensions</artifactId>
-    <version>5.0.1-SNAPSHOT</version>
+    <version>5.1.1-SNAPSHOT</version>
     <name>kotlin-extensions</name>
     <url>http://maven.apache.org</url>
     <properties>
@@ -39,7 +39,7 @@
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>rest-assured</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
         </dependency>
 
         <!-- Test -->
diff --git a/modules/pom.xml b/modules/pom.xml
index 4a999cea..b953843a 100644
--- a/modules/pom.xml
+++ b/modules/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>rest-assured-parent</artifactId>
         <groupId>io.rest-assured</groupId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>modules</artifactId>
diff --git a/modules/scala-support/pom.xml b/modules/scala-support/pom.xml
index b26bdb35..6fabaac9 100644
--- a/modules/scala-support/pom.xml
+++ b/modules/scala-support/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <groupId>io.rest-assured</groupId>
         <artifactId>modules</artifactId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -79,7 +79,7 @@
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>rest-assured</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>junit</groupId>
diff --git a/modules/spring-commons/pom.xml b/modules/spring-commons/pom.xml
index ba2a3632..f5422443 100644
--- a/modules/spring-commons/pom.xml
+++ b/modules/spring-commons/pom.xml
@@ -19,10 +19,10 @@
     <parent>
         <groupId>io.rest-assured</groupId>
         <artifactId>modules</artifactId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <artifactId>spring-commons</artifactId>
-    <version>5.0.1-SNAPSHOT</version>
+    <version>5.1.1-SNAPSHOT</version>
     <name>spring-commons</name>
     <url>http://maven.apache.org</url>
     <properties>
@@ -34,7 +34,7 @@
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>rest-assured</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
         </dependency>
     </dependencies>
 </project>
\ No newline at end of file
diff --git a/modules/spring-mock-mvc-kotlin-extensions/pom.xml b/modules/spring-mock-mvc-kotlin-extensions/pom.xml
index 40b498a8..da1c483f 100644
--- a/modules/spring-mock-mvc-kotlin-extensions/pom.xml
+++ b/modules/spring-mock-mvc-kotlin-extensions/pom.xml
@@ -20,11 +20,11 @@
     <parent>
         <artifactId>modules</artifactId>
         <groupId>io.rest-assured</groupId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
 
     <artifactId>spring-mock-mvc-kotlin-extensions</artifactId>
-    <version>5.0.1-SNAPSHOT</version>
+    <version>5.1.1-SNAPSHOT</version>
     <name>spring-mock-mvc-kotlin-extensions</name>
     <url>http://maven.apache.org</url>
     <properties>
@@ -40,7 +40,7 @@
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>spring-mock-mvc</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>javax.servlet</groupId>
diff --git a/modules/spring-mock-mvc/pom.xml b/modules/spring-mock-mvc/pom.xml
index 3ae387e9..d3e5fc2c 100644
--- a/modules/spring-mock-mvc/pom.xml
+++ b/modules/spring-mock-mvc/pom.xml
@@ -20,10 +20,10 @@
     <parent>
         <groupId>io.rest-assured</groupId>
         <artifactId>modules</artifactId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <artifactId>spring-mock-mvc</artifactId>
-    <version>5.0.1-SNAPSHOT</version>
+    <version>5.1.1-SNAPSHOT</version>
     <name>spring-mock-mvc</name>
     <url>http://maven.apache.org</url>
     <properties>
@@ -36,12 +36,12 @@
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>rest-assured</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>spring-commons</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.springframework</groupId>
@@ -71,6 +71,12 @@
             <version>3.1.0</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>jakarta.platform</groupId>
+            <artifactId>jakarta.jakartaee-web-api</artifactId>
+            <version>9.1.0</version>
+            <scope>provided</scope>
+        </dependency>
 
         <!-- Test -->
         <dependency>
diff --git a/modules/spring-mock-mvc/src/main/java/io/restassured/module/mockmvc/internal/MockMvcRequestSenderImpl.java b/modules/spring-mock-mvc/src/main/java/io/restassured/module/mockmvc/internal/MockMvcRequestSenderImpl.java
index 865f4523..bd7f7fba 100644
--- a/modules/spring-mock-mvc/src/main/java/io/restassured/module/mockmvc/internal/MockMvcRequestSenderImpl.java
+++ b/modules/spring-mock-mvc/src/main/java/io/restassured/module/mockmvc/internal/MockMvcRequestSenderImpl.java
@@ -45,7 +45,6 @@ import io.restassured.specification.ResponseSpecification;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.MediaType;
-import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.mock.web.MockMultipartFile;
 import org.springframework.test.web.servlet.MockMvc;
@@ -57,9 +56,12 @@ import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequ
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
 import org.springframework.test.web.servlet.request.RequestPostProcessor;
 import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import org.springframework.util.ReflectionUtils;
 import org.springframework.web.util.UriComponentsBuilder;
 
 import java.io.*;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
 import java.net.URI;
 import java.net.URL;
 import java.security.Principal;
@@ -67,6 +69,7 @@ import java.util.*;
 import java.util.concurrent.TimeUnit;
 
 import static io.restassured.internal.common.assertion.AssertParameter.notNull;
+import static io.restassured.internal.common.classpath.ClassPathResolver.existInCP;
 import static io.restassured.internal.support.PathSupport.mergeAndRemoveDoubleSlash;
 import static io.restassured.module.mockmvc.internal.SpringSecurityClassPathChecker.isSpringSecurityInClasspath;
 import static io.restassured.module.spring.commons.HeaderHelper.mapToArray;
@@ -81,7 +84,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
 class MockMvcRequestSenderImpl implements MockMvcRequestSender, MockMvcRequestAsyncConfigurer, MockMvcRequestAsyncSender {
     private static final String ATTRIBUTE_NAME_URL_TEMPLATE = "org.springframework.restdocs.urlTemplate";
     private static final String CONTENT_TYPE = "Content-Type";
-    private static final String CHARSET = "charset";
+    private static final boolean isSpring6OrLater = existInCP("org.springframework.core.annotation.CoreAnnotationsRuntimeHintsRegistrar");
 
     private final MockMvc mockMvc;
     private final Map<String, Object> params;
@@ -153,22 +156,32 @@ class MockMvcRequestSenderImpl implements MockMvcRequestSender, MockMvcRequestAs
         return new Headers(headers);
     }
 
-    private Cookies convertCookies(javax.servlet.http.Cookie[] servletCookies) {
+    private Cookies convertCookies(Object[] servletCookies) {
         List<Cookie> cookies = new ArrayList<>();
-        for (javax.servlet.http.Cookie servletCookie : servletCookies) {
-            Cookie.Builder cookieBuilder = new Cookie.Builder(servletCookie.getName(), servletCookie.getValue());
-            if (servletCookie.getComment() != null) {
-                cookieBuilder.setComment(servletCookie.getComment());
+        for (Object servletCookie : servletCookies) {
+            Cookie.Builder cookieBuilder = new Cookie.Builder(
+                    invokeMethod(servletCookie, "getName"),
+                    invokeMethod(servletCookie, "getValue")
+            );
+
+            String comment = invokeMethod(servletCookie, "getComment");
+            if (comment != null) {
+                cookieBuilder.setComment(comment);
             }
-            if (servletCookie.getDomain() != null) {
-                cookieBuilder.setDomain(servletCookie.getDomain());
+
+            String domain = invokeMethod(servletCookie, "getDomain");
+            if (domain != null) {
+                cookieBuilder.setDomain(domain);
             }
-            if (servletCookie.getPath() != null) {
-                cookieBuilder.setPath(servletCookie.getPath());
+
+            String path = invokeMethod(servletCookie, "getPath");
+            if (path != null) {
+                cookieBuilder.setPath(path);
             }
-            cookieBuilder.setMaxAge(servletCookie.getMaxAge());
-            cookieBuilder.setVersion(servletCookie.getVersion());
-            cookieBuilder.setSecured(servletCookie.getSecure());
+            int getMaxAge = invokeMethod(servletCookie, "getMaxAge");
+            cookieBuilder.setMaxAge(getMaxAge);
+            cookieBuilder.setVersion(invokeMethod(servletCookie, "getVersion"));
+            cookieBuilder.setSecured(invokeMethod(servletCookie, "getSecure"));
             cookies.add(cookieBuilder.build());
         }
         return new Cookies(cookies);
@@ -218,7 +231,7 @@ class MockMvcRequestSenderImpl implements MockMvcRequestSender, MockMvcRequestAs
             restAssuredResponse.setFilterContextProperties(new HashMap() {{
                 put(TimingFilter.RESPONSE_TIME_MILLISECONDS, responseTime);
             }});
-            restAssuredResponse.setCookies(convertCookies(response.getCookies()));
+            restAssuredResponse.setCookies(convertCookies(invokeMethod(response, "getCookies")));
 
             if (responseSpecification != null) {
                 responseSpecification.validate(ResponseConverter.toStandardResponse(restAssuredResponse));
@@ -292,13 +305,14 @@ class MockMvcRequestSenderImpl implements MockMvcRequestSender, MockMvcRequestAs
         if (multiParts.isEmpty()) {
             request = MockMvcRequestBuilders.request(method, uri, pathParams);
         } else if (method == POST || method == PUT) {
-            request = MockMvcRequestBuilders.fileUpload(uri, pathParams);
-            request.with(new RequestPostProcessor() {
-                @Override
-                public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
-                    request.setMethod(method.name());
-                    return request;
-                }
+            if (isSpring6OrLater) {
+                request = invokeMethod(MockMvcRequestBuilders.class, "multipart", uri, pathParams);
+            } else {
+                request = invokeMethod(MockMvcRequestBuilders.class, "fileUpload", new Class[]{String.class, Object[].class}, uri, pathParams);
+            }
+            request.with(req -> {
+                req.setMethod(method.name());
+                return req;
             });
         } else {
             throw new IllegalArgumentException("Currently multi-part file data uploading only works for POST and PUT methods");
@@ -362,24 +376,31 @@ class MockMvcRequestSenderImpl implements MockMvcRequestSender, MockMvcRequestAs
 
         if (cookies.exist()) {
             for (Cookie cookie : cookies) {
-                javax.servlet.http.Cookie servletCookie = new javax.servlet.http.Cookie(cookie.getName(), cookie.getValue());
+                final String cookieClassName;
+                if (isSpring6OrLater) {
+                    cookieClassName = "jakarta.servlet.http.Cookie";
+                } else {
+                    cookieClassName = "javax.servlet.http.Cookie";
+                }
+                final Object servletCookie = invokeConstructor(cookieClassName, cookie.getName(), cookie.getValue());
+
                 if (cookie.hasComment()) {
-                    servletCookie.setComment(cookie.getComment());
+                    invokeMethod(servletCookie, "setComment", cookie.getComment());
                 }
                 if (cookie.hasDomain()) {
-                    servletCookie.setDomain(cookie.getDomain());
+                    invokeMethod(servletCookie, "setDomain", cookie.getDomain());
                 }
                 if (cookie.hasMaxAge()) {
-                    servletCookie.setMaxAge((int) cookie.getMaxAge());
+                    invokeMethod(servletCookie, "setMaxAge", (int) cookie.getMaxAge());
                 }
                 if (cookie.hasPath()) {
-                    servletCookie.setPath(cookie.getPath());
+                    invokeMethod(servletCookie, "setPath", cookie.getPath());
                 }
                 if (cookie.hasVersion()) {
-                    servletCookie.setVersion(cookie.getVersion());
+                    invokeMethod(servletCookie, "setVersion", cookie.getVersion());
                 }
-                servletCookie.setSecure(cookie.isSecured());
-                request.cookie(servletCookie);
+                invokeMethod(servletCookie, "setSecure", new Class[]{boolean.class}, cookie.isSecured());
+                invokeMethod(request, "cookie", new Class[]{arrayNameOf(cookieClassName)}, servletCookie);
             }
         }
 
@@ -435,6 +456,14 @@ class MockMvcRequestSenderImpl implements MockMvcRequestSender, MockMvcRequestAs
         return performRequest(request);
     }
 
+    private Class<?> arrayNameOf(String cookieClassName) {
+        try {
+            return Class.forName("[L" + cookieClassName + ";");
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     private void setContentTypeToApplicationFormUrlEncoded(MockHttpServletRequestBuilder request) {
         MediaType mediaType = MediaType.parseMediaType(HeaderHelper.buildApplicationFormEncodedContentType(config, APPLICATION_FORM_URLENCODED_VALUE));
         request.contentType(mediaType);
@@ -681,4 +710,57 @@ class MockMvcRequestSenderImpl implements MockMvcRequestSender, MockMvcRequestAs
         }
         return httpMethod;
     }
+
+    private static <T> T invokeMethod(Object instance, String methodName, Object... arguments) {
+        Class<?>[] argumentTypes = getArgumentTypes(arguments);
+        return invokeMethod(instance, methodName, argumentTypes, arguments);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> T invokeMethod(Object instance, String methodName, Class<?>[] argumentTypes, Object... arguments) {
+        java.lang.reflect.Method method = ReflectionUtils.findMethod(instance instanceof Class ? (Class<?>) instance : instance.getClass(), methodName, argumentTypes);
+        if (method == null) {
+            throw new IllegalArgumentException("Cannot find method '" + methodName + "' in " + instance.getClass() + " (arguments=" + Arrays.toString(arguments) + ")");
+        }
+        if (method.isVarArgs()) {
+            Class<?> argumentType = argumentTypes[argumentTypes.length - 1];
+            if (argumentType.isArray()) {
+                argumentType = argumentType.getComponentType();
+            }
+            int numberOfVarArgParameters = arguments.length - argumentTypes.length + 1;
+            Object varArgsArguments = Array.newInstance(argumentType, numberOfVarArgParameters);
+            for (int i = arguments.length - argumentTypes.length; i < numberOfVarArgParameters; i++) {
+                Array.set(varArgsArguments, i, arguments[i]);
+            }
+
+            Object[] objectArrayNeededForInvocation = new Object[argumentTypes.length];
+            if (arguments.length - 1 >= 0) {
+                System.arraycopy(arguments, 0, objectArrayNeededForInvocation, 0, arguments.length - 1);
+            }
+            objectArrayNeededForInvocation[argumentTypes.length - 1] = varArgsArguments;
+            return (T) ReflectionUtils.invokeMethod(method, instance, objectArrayNeededForInvocation);
+        } else {
+            return (T) ReflectionUtils.invokeMethod(method, instance, arguments);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> T invokeConstructor(String className, Object... arguments) {
+        Class<?>[] argumentTypes = getArgumentTypes(arguments);
+        try {
+            Class<T> cls = (Class<T>) Class.forName(className);
+            Constructor<T> constructor = cls.getConstructor(argumentTypes);
+            return constructor.newInstance(arguments);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static Class<?>[] getArgumentTypes(Object[] arguments) {
+        Class<?>[] argumentTypes = new Class[arguments.length];
+        for (int i = 0; i < arguments.length; i++) {
+            argumentTypes[i] = arguments[i].getClass();
+        }
+        return argumentTypes;
+    }
 }
diff --git a/pom.xml b/pom.xml
index a90f317f..19c7ae8e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,7 +26,7 @@
     <groupId>io.rest-assured</groupId>
     <artifactId>rest-assured-parent</artifactId>
     <packaging>pom</packaging>
-    <version>5.0.1-SNAPSHOT</version>
+    <version>5.1.1-SNAPSHOT</version>
     <url>http://code.google.com/p/rest-assured</url>
     <name>REST Assured Parent POM</name>
     <description>Java DSL for easy testing of REST services</description>
@@ -69,10 +69,10 @@
         <jakarta.json.version>1.1.6</jakarta.json.version>
         <maven-javadoc.version>2.9.1</maven-javadoc.version>
         <surefire.version>2.22.0</surefire.version>
-        <kotlin.version>1.6.10</kotlin.version>
+        <kotlin.version>1.6.21</kotlin.version>
         <assertj-core.version>3.16.1</assertj-core.version>
         <scribe.version>2.5.3</scribe.version>
-        <sundrio.version>0.80.1</sundrio.version>
+        <sundrio.version>0.90.4</sundrio.version>
     </properties>
     <scm>
         <url>http://github.com/rest-assured/rest-assured/tree/${scm.branch}</url>
@@ -448,7 +448,7 @@
             <dependency>
                 <groupId>com.google.code.gson</groupId>
                 <artifactId>gson</artifactId>
-                <version>2.6.2</version>
+                <version>2.9.0</version>
             </dependency>
             <dependency>
                 <groupId>com.googlecode.json-simple</groupId>
diff --git a/rest-assured-all/pom.xml b/rest-assured-all/pom.xml
index 7355c622..80f74de3 100644
--- a/rest-assured-all/pom.xml
+++ b/rest-assured-all/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>rest-assured-parent</artifactId>
         <groupId>io.rest-assured</groupId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/rest-assured-common/pom.xml b/rest-assured-common/pom.xml
index b6064934..7c63efee 100644
--- a/rest-assured-common/pom.xml
+++ b/rest-assured-common/pom.xml
@@ -20,12 +20,12 @@
     <parent>
         <groupId>io.rest-assured</groupId>
         <artifactId>rest-assured-parent</artifactId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <groupId>io.rest-assured</groupId>
     <artifactId>rest-assured-common</artifactId>
     <packaging>bundle</packaging>
-    <version>5.0.1-SNAPSHOT</version>
+    <version>5.1.1-SNAPSHOT</version>
     <name>rest-assured-common</name>
     <url>http://maven.apache.org</url>
     <properties>
diff --git a/rest-assured-common/src/main/groovy/io/restassured/internal/common/mapper/ObjectDeserializationContextImpl.groovy b/rest-assured-common/src/main/groovy/io/restassured/internal/common/mapper/ObjectDeserializationContextImpl.groovy
deleted file mode 100644
index f069fbce..00000000
--- a/rest-assured-common/src/main/groovy/io/restassured/internal/common/mapper/ObjectDeserializationContextImpl.groovy
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2019 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-
-package io.restassured.internal.common.mapper
-
-import io.restassured.common.mapper.DataToDeserialize
-import io.restassured.common.mapper.ObjectDeserializationContext
-
-import java.lang.reflect.Type
-
-class ObjectDeserializationContextImpl implements ObjectDeserializationContext {
-
-    def DataToDeserialize dataToDeserialize
-    def Type type
-    def charset
-
-    @Override
-    DataToDeserialize getDataToDeserialize() {
-        return dataToDeserialize
-    }
-
-    @Override
-    Type getType() {
-        return type
-    }
-
-    @Override
-    String getCharset() {
-        return charset
-    }
-}
diff --git a/rest-assured-common/src/main/java/io/restassured/internal/common/ObjectDeserializationContextImpl.java b/rest-assured-common/src/main/java/io/restassured/internal/common/ObjectDeserializationContextImpl.java
new file mode 100644
index 00000000..47f7ce55
--- /dev/null
+++ b/rest-assured-common/src/main/java/io/restassured/internal/common/ObjectDeserializationContextImpl.java
@@ -0,0 +1,50 @@
+package io.restassured.internal.common;
+
+import io.restassured.common.mapper.DataToDeserialize;
+import io.restassured.common.mapper.ObjectDeserializationContext;
+
+import java.lang.reflect.Type;
+
+public class ObjectDeserializationContextImpl implements ObjectDeserializationContext {
+
+    private DataToDeserialize dataToDeserialize;
+    private Type type;
+    private String charset;
+
+    // Used by groovy
+    public ObjectDeserializationContextImpl() {
+    }
+
+    public ObjectDeserializationContextImpl(DataToDeserialize dataToDeserialize, Type type, String charset) {
+        this.dataToDeserialize = dataToDeserialize;
+        this.type = type;
+        this.charset = charset;
+    }
+
+    @Override
+    public DataToDeserialize getDataToDeserialize() {
+        return dataToDeserialize;
+    }
+
+    @Override
+    public Type getType() {
+        return type;
+    }
+
+    @Override
+    public String getCharset() {
+        return charset;
+    }
+
+    public void setDataToDeserialize(DataToDeserialize dataToDeserialize) {
+        this.dataToDeserialize = dataToDeserialize;
+    }
+
+    public void setType(Type type) {
+        this.type = type;
+    }
+
+    public void setCharset(String charset) {
+        this.charset = charset;
+    }
+}
\ No newline at end of file
diff --git a/rest-assured/pom.xml b/rest-assured/pom.xml
index 82df1f05..a2335687 100644
--- a/rest-assured/pom.xml
+++ b/rest-assured/pom.xml
@@ -22,12 +22,12 @@
     <parent>
         <groupId>io.rest-assured</groupId>
         <artifactId>rest-assured-parent</artifactId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
 
     <groupId>io.rest-assured</groupId>
     <artifactId>rest-assured</artifactId>
-    <version>5.0.1-SNAPSHOT</version>
+    <version>5.1.1-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <name>REST Assured</name>
@@ -56,6 +56,7 @@
                             io.restassured.*
                         </Export-Package>
                         <Import-Package>
+                            groovy.*;version="${groovy.range}",
                             org.apache.groovy.*;version="${groovy.range}",
                             *
                         </Import-Package>
@@ -125,12 +126,12 @@
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>json-path</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>xml-path</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
         </dependency>
         <!-- Optional dependencies -->
         <dependency>
diff --git a/rest-assured/src/main/groovy/io/restassured/assertion/BodyMatcher.groovy b/rest-assured/src/main/groovy/io/restassured/assertion/BodyMatcher.groovy
deleted file mode 100644
index 3b8e2f24..00000000
--- a/rest-assured/src/main/groovy/io/restassured/assertion/BodyMatcher.groovy
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright 2019 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package io.restassured.assertion
-
-import io.restassured.config.RestAssuredConfig
-import io.restassured.internal.ResponseParserRegistrar
-import io.restassured.response.Response
-import org.hamcrest.Description
-import org.hamcrest.Matcher
-import org.hamcrest.StringDescription
-import org.hamcrest.xml.HasXPath
-import org.w3c.dom.Element
-
-import javax.xml.parsers.DocumentBuilderFactory
-
-import static io.restassured.config.MatcherConfig.ErrorDescriptionType.REST_ASSURED
-import static java.lang.String.format
-import static org.apache.commons.lang3.StringUtils.*
-
-class BodyMatcher {
-  private static final String XPATH = "XPath"
-  def key
-  Matcher matcher
-  ResponseParserRegistrar rpr
-
-  def validate(Response response, contentParser, RestAssuredConfig config) {
-    def success = true
-    def errorMessage = ""
-
-    contentParser = fallbackToResponseBodyIfContentParserIsNull(response, contentParser)
-    if (key == null) {
-      if (isXPathMatcher()) {
-        def xmlConfig = config.getXmlConfig()
-        boolean namespaceAware = xmlConfig.isNamespaceAware()
-        Map<String, Boolean> features = xmlConfig.features()
-
-        def factory = DocumentBuilderFactory.newInstance()
-        factory.setNamespaceAware(namespaceAware)
-        if (!features.isEmpty()) {
-          features.each { featureName, isEnabled ->
-            factory.setFeature(featureName, isEnabled)
-          }
-        }
-
-        def properties = xmlConfig.properties()
-        if (!properties.isEmpty()) {
-          properties.each { name, value ->
-            factory.setAttribute(name, value)
-          }
-        }
-
-        Element node = factory.newDocumentBuilder().parse(new ByteArrayInputStream(response.asByteArray())).getDocumentElement()
-        if (!matcher.matches(node)) {
-          success = false
-          if (config.matcherConfig.hasErrorDescriptionType(REST_ASSURED)) {
-            errorMessage = format("Expected: %s\n  Actual: %s\n", trim(matcher.toString()), contentParser)
-          } else {
-            errorMessage = getDescription(matcher, contentParser)
-          }
-        }
-      } else if (!matcher.matches(response.asString())) {
-        success = false
-        if (config.matcherConfig.hasErrorDescriptionType(REST_ASSURED)) {
-          errorMessage = "Response body doesn't match expectation.\nExpected: $matcher\n  Actual: $contentParser\n"
-        } else {
-          errorMessage = format("Response body doesn't match expectation.\n%s", getDescription(matcher, response.asString()))
-        }
-      }
-    } else {
-      def assertion = StreamVerifier.newAssertion(response, key, rpr)
-      def result = null
-      if (contentParser != null) {
-        if (contentParser instanceof String) {
-          // This happens for example when expecting JSON/XML assertion but response content is empty
-          def isEmpty = contentParser?.isEmpty()
-          errorMessage = format("Cannot assert that path \"$key\" matches $matcher because the response body %s.", isEmpty ? "is empty" : "equal to \"$contentParser\"")
-          success = false
-        } else {
-          result = assertion.getResult(contentParser, config)
-        }
-      }
-
-      if (success && !matcher.matches(result)) {
-        success = false
-        if (config.matcherConfig.hasErrorDescriptionType(REST_ASSURED)) {
-          if (result instanceof Object[]) {
-            result = result.join(",")
-          }
-          errorMessage = format("%s %s doesn't match.\nExpected: %s\n  Actual: %s\n",
-                  assertion.description(),
-                  key,
-                  removeQuotesIfString(matcher.toString()),
-                  removeQuotesIfString(new StringDescription().appendValue(result).toString()))
-        } else {
-          errorMessage = format("%s %s doesn't match.\n%s", assertion.description(), key, getDescription(matcher, result))
-        }
-      }
-    }
-    return [success: success, errorMessage: errorMessage]
-  }
-
-  private static String getDescription(Matcher matcher, Object actual) {
-    Description description = new StringDescription()
-    description.appendText("\nExpected: ")
-            .appendDescriptionOf(matcher)
-            .appendText("\n  Actual: ")
-    matcher.describeMismatch(actual, description)
-    return description.toString()
-  }
-
-  private static String removeQuotesIfString(String string) {
-    if (startsWith(string, "\"") && endsWith(string, "\"")) {
-      def start = removeStart(string, "\"")
-      string = removeEnd(start, "\"")
-    }
-    string
-  }
-
-  static def fallbackToResponseBodyIfContentParserIsNull(Response response, contentParser) {
-    if (contentParser == null) {
-      return response.asString()
-    }
-    return contentParser
-  }
-
-  private boolean isXPathMatcher() {
-    def isNestedMatcherContainingXPathMatcher = {
-      def description = new StringDescription()
-      matcher.describeTo(description)
-      description.toString().contains(XPATH)
-    }
-
-    matcher instanceof HasXPath || isNestedMatcherContainingXPathMatcher()
-  }
-
-  boolean requiresTextParsing() {
-    key == null || isXPathMatcher()
-  }
-
-  boolean requiresPathParsing() {
-    !requiresTextParsing()
-  }
-}
diff --git a/rest-assured/src/main/groovy/io/restassured/assertion/BodyMatcherGroup.groovy b/rest-assured/src/main/groovy/io/restassured/assertion/BodyMatcherGroup.groovy
deleted file mode 100644
index 8cc0d11e..00000000
--- a/rest-assured/src/main/groovy/io/restassured/assertion/BodyMatcherGroup.groovy
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2019 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-package io.restassured.assertion
-
-import io.restassured.config.RestAssuredConfig
-import io.restassured.response.Response
-
-class BodyMatcherGroup {
-    private def bodyAssertions = []
-
-    def leftShift(Object bodyMatcher) {
-        if (bodyMatcher instanceof BodyMatcherGroup) {
-            bodyMatcher.bodyAssertions.each {
-                bodyAssertions << it
-            }
-        } else {
-            bodyAssertions << bodyMatcher
-        }
-    }
-
-
-    def reset() {
-        bodyAssertions.clear()
-    }
-
-    def size() {
-        bodyAssertions.size()
-    }
-
-    def List validate(Response response, contentParser, RestAssuredConfig config) {
-        bodyAssertions.collect { it.validate(response, contentParser, config) }
-    }
-
-    public boolean containsMatchers() {
-        !bodyAssertions.isEmpty()
-    }
-
-    def boolean requiresTextParsing() {
-        bodyAssertions.any { matcher -> matcher.requiresTextParsing() }
-    }
-
-    def boolean requiresPathParsing() {
-        bodyAssertions.any { matcher -> matcher.requiresPathParsing() }
-    }
-}
diff --git a/rest-assured/src/main/groovy/io/restassured/assertion/CookieMatcher.groovy b/rest-assured/src/main/groovy/io/restassured/assertion/CookieMatcher.groovy
deleted file mode 100644
index 547f5938..00000000
--- a/rest-assured/src/main/groovy/io/restassured/assertion/CookieMatcher.groovy
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright 2019 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.restassured.assertion
-
-import io.restassured.http.Cookie
-import io.restassured.http.Cookies
-import org.apache.commons.lang3.StringUtils
-import org.apache.commons.logging.Log
-import org.apache.commons.logging.LogFactory
-import org.apache.http.client.utils.DateUtils
-import org.hamcrest.Matcher
-import org.hamcrest.StringDescription
-
-import static io.restassured.http.Cookie.*
-import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase
-import static org.apache.commons.lang3.StringUtils.trim
-
-class CookieMatcher {
-    private static final Log log = LogFactory.getLog(CookieMatcher.class)
-
-    def cookieName
-    Matcher<String> matcher
-
-    def validateCookies(List<String> headerWithCookieList, Cookies responseCookies) {
-        def success = true
-        def errorMessage = ""
-        if(!headerWithCookieList && !responseCookies.exist()) {
-            success = false
-            errorMessage = "No cookies defined in the response\n"
-        } else {
-            Cookies cookiesInHeader = getCookies(headerWithCookieList)
-            List<Cookie> mergedCookies = []
-            mergedCookies += cookiesInHeader
-            for (Cookie responseCookie: responseCookies) {
-                if (!cookiesInHeader.hasCookieWithName(responseCookie.getName())) {
-                    mergedCookies << responseCookie
-                }
-            }
-
-            def raCookies = new Cookies(mergedCookies)
-            def cookie = raCookies.get(cookieName)
-            if (cookie == null) {
-                String cookiesAsString = raCookies.toString()
-                success = false
-                errorMessage = "Cookie \"$cookieName\" was not defined in the response. Cookies are: \n$cookiesAsString\n"
-            } else {
-                def value = cookie.getValue()
-                if(!matcher.matches(value)) {
-                    success = false
-                    def expectedDescription = getExpectedDescription(matcher)
-                    def mismatchDescription = getMismatchDescription(matcher, value)
-                    errorMessage = "Expected cookie \"$cookieName\" was not $expectedDescription, $mismatchDescription.\n"
-
-                }
-            }
-        }
-        [success: success, errorMessage: errorMessage]
-    }
-
-    static String getExpectedDescription(Matcher matcher) {
-        def expectedDescription = new StringDescription()
-        matcher.describeTo(expectedDescription)
-        return expectedDescription.toString()
-    }
-
-    static String getMismatchDescription(Matcher matcher, value) {
-        def mismatchDescription = new StringDescription()
-        matcher.describeMismatch(value, mismatchDescription)
-        return mismatchDescription.toString()
-    }
-
-    static Cookies getCookies(headerWithCookieList) {
-        def cookieList = []
-        headerWithCookieList.each {
-            Cookie.Builder cookieBuilder
-            def cookieStrings = StringUtils.split(it, ";")
-            cookieStrings.eachWithIndex { part, index ->
-                if(index == 0) {
-                    if(part.contains("=")) {
-                        def (cookieKey, cookieValue) = getKeyAndValueOfCookie(part)
-                        cookieBuilder = new Cookie.Builder(cookieKey, cookieValue)
-                    } else {
-                        cookieBuilder = new Cookie.Builder(part, null)
-                    }
-                } else if(part.contains("=")) {
-                    def (cookieKey, cookieValue) = getKeyAndValueOfCookie(part)
-                    setCookieProperty(cookieBuilder, cookieKey, cookieValue)
-                } else {
-                    setCookieProperty(cookieBuilder, part, null)
-                }
-            }
-            cookieList << cookieBuilder?.build()
-        }
-        return new Cookies(cookieList)
-    }
-
-    static List getKeyAndValueOfCookie(String part) {
-        def indexOfEqual = StringUtils.indexOf(part, "=")
-        def cookieKey, cookieValue
-        if(indexOfEqual > -1) {
-            cookieKey = StringUtils.substring(part, 0, indexOfEqual)
-            cookieValue = StringUtils.substring(part, indexOfEqual + 1)
-        } else {
-            cookieKey = part
-            cookieValue = null
-        }
-        return [StringUtils.trim(cookieKey), StringUtils.trim(cookieValue)]
-    }
-
-    private static def setCookieProperty(Cookie.Builder builder, name, value) {
-        name = trim(name)
-        if(value != null || equalsIgnoreCase(name, SECURE) || equalsIgnoreCase(name, HTTP_ONLY)) {
-            if(equalsIgnoreCase(name, COMMENT)) {
-                builder.setComment(value)
-            } else if(equalsIgnoreCase(name, VERSION)) {
-              // Some servers supply the version in quotes, remove them
-              value = trim(StringUtils.remove(value, "\""))
-                if (value.isNumber()) {
-                  builder.setVersion(value as Integer)
-                }
-            } else if(equalsIgnoreCase(name, PATH)) {
-                builder.setPath(value)
-            } else if(equalsIgnoreCase(name, DOMAIN)) {
-                builder.setDomain(value)
-            } else if(equalsIgnoreCase(name, MAX_AGE)) {
-                builder.setMaxAge(Long.parseLong(value))
-            } else if(equalsIgnoreCase(name, SECURE)) {
-                builder.setSecured(true)
-            } else if(equalsIgnoreCase(name, HTTP_ONLY)) {
-                builder.setHttpOnly(true)
-            } else if(equalsIgnoreCase(name, EXPIRES)) {
-              value = trim(StringUtils.remove(value, "\""))
-              Date parsedDate = DateUtils.parseDate(value)
-              if (parsedDate != null) {
-                builder.setExpiryDate(parsedDate)
-              } else {
-                log.warn("Ignoring unparsable 'Expires' attribute value: " + value)
-              }
-            } else if(equalsIgnoreCase(name, SAME_SITE)) {
-                builder.setSameSite(value)
-            }
-        }
-    }
-}
diff --git a/rest-assured/src/main/groovy/io/restassured/assertion/DetailedCookieAssertion.groovy b/rest-assured/src/main/groovy/io/restassured/assertion/DetailedCookieAssertion.groovy
index 9ba9a618..ec337217 100644
--- a/rest-assured/src/main/groovy/io/restassured/assertion/DetailedCookieAssertion.groovy
+++ b/rest-assured/src/main/groovy/io/restassured/assertion/DetailedCookieAssertion.groovy
@@ -18,6 +18,7 @@ package io.restassured.assertion
 
 import io.restassured.http.Cookie
 import io.restassured.http.Cookies
+import io.restassured.internal.assertion.CookieMatcher
 import org.hamcrest.Matcher
 
 
diff --git a/rest-assured/src/main/groovy/io/restassured/internal/RequestSpecificationImpl.groovy b/rest-assured/src/main/groovy/io/restassured/internal/RequestSpecificationImpl.groovy
index 802190ef..6e52531d 100644
--- a/rest-assured/src/main/groovy/io/restassured/internal/RequestSpecificationImpl.groovy
+++ b/rest-assured/src/main/groovy/io/restassured/internal/RequestSpecificationImpl.groovy
@@ -1531,7 +1531,7 @@ class RequestSpecificationImpl implements FilterableRequestSpecification, Groovy
         contentType = URLENC
       } else if (requestBody == null) {
         contentType = mayHaveBody(method) ? URLENC : null
-      } else if (requestBody instanceof byte[]) {
+      } else if (requestBody instanceof byte[] || requestBody instanceof InputStream) {
         contentType = BINARY
       } else {
         contentType = TEXT
diff --git a/rest-assured/src/main/groovy/io/restassured/internal/ResponseSpecificationImpl.groovy b/rest-assured/src/main/groovy/io/restassured/internal/ResponseSpecificationImpl.groovy
index 4bf96d31..90afa3a1 100644
--- a/rest-assured/src/main/groovy/io/restassured/internal/ResponseSpecificationImpl.groovy
+++ b/rest-assured/src/main/groovy/io/restassured/internal/ResponseSpecificationImpl.groovy
@@ -22,6 +22,9 @@ import io.restassured.config.RestAssuredConfig
 import io.restassured.filter.log.LogDetail
 import io.restassured.http.ContentType
 import io.restassured.internal.MapCreator.CollisionStrategy
+import io.restassured.internal.assertion.BodyMatcher
+import io.restassured.internal.assertion.BodyMatcherGroup
+import io.restassured.internal.assertion.CookieMatcher
 import io.restassured.internal.log.LogRepository
 import io.restassured.internal.util.MatcherErrorMessageBuilder
 import io.restassured.listener.ResponseValidationFailureListener
@@ -46,6 +49,10 @@ class ResponseSpecificationImpl implements FilterableResponseSpecification {
 
   private static final String EMPTY = ""
   private static final String DOT = "."
+  private static final String SAFE_REF = "?."
+  private static final String SAFE_INDEX = "?["
+  private static final String SPREAD_REF = "*."
+
   private Matcher<Integer> expectedStatusCode
   private Matcher<String> expectedStatusLine
   private BodyMatcherGroup bodyMatchers = new BodyMatcherGroup()
@@ -98,9 +105,9 @@ class ResponseSpecificationImpl implements FilterableResponseSpecification {
   ResponseSpecification body(Matcher matcher, Matcher... additionalMatchers) {
     notNull(matcher, "matcher")
     validateResponseIfRequired {
-      bodyMatchers << new BodyMatcher(key: null, matcher: matcher, rpr: rpr)
+      bodyMatchers.add(new BodyMatcher(key: null, matcher: matcher, rpr: rpr))
       additionalMatchers?.each { hamcrestMatcher ->
-        bodyMatchers << new BodyMatcher(key: null, matcher: hamcrestMatcher, rpr: rpr)
+        bodyMatchers.add(new BodyMatcher(key: null, matcher: hamcrestMatcher, rpr: rpr))
       }
     }
     return this
@@ -261,7 +268,7 @@ class ResponseSpecificationImpl implements FilterableResponseSpecification {
     notNull(matcher, "matcher")
 
     validateResponseIfRequired {
-      bodyMatchers << new BodyMatcher(key: applyArguments(mergeKeyWithRootPath(key), arguments), matcher: matcher, rpr: rpr)
+      bodyMatchers.add(new BodyMatcher(key: applyArguments(mergeKeyWithRootPath(key), arguments), matcher: matcher, rpr: rpr))
       if (additionalKeyMatcherPairs?.length > 0) {
         def pairs = MapCreator.createMapFromObjects(CollisionStrategy.MERGE, additionalKeyMatcherPairs)
         pairs.each { matchingKey, matchingValue ->
@@ -293,10 +300,10 @@ class ResponseSpecificationImpl implements FilterableResponseSpecification {
                 keyToUse = keyWithRoot
                 matcherToUse = m
               }
-              bodyMatchers << new BodyMatcher(key: keyToUse, matcher: matcherToUse, rpr: rpr)
+              bodyMatchers.add(new BodyMatcher(key: keyToUse, matcher: matcherToUse, rpr: rpr))
             }
           } else {
-            bodyMatchers << new BodyMatcher(key: keyWithRoot, matcher: hamcrestMatcher, rpr: rpr)
+            bodyMatchers.add(new BodyMatcher(key: keyWithRoot, matcher: hamcrestMatcher, rpr: rpr))
           }
         }
       }
@@ -641,7 +648,9 @@ class ResponseSpecificationImpl implements FilterableResponseSpecification {
     if (bodyRootPath != null && bodyRootPath != EMPTY) {
       if (bodyRootPath.endsWith(DOT) && key.startsWith(DOT)) {
         return bodyRootPath + substringAfter(key, DOT)
-      } else if (!bodyRootPath.endsWith(DOT) && !key.startsWith(DOT) && !key.startsWith("[")) {
+      } else if (!bodyRootPath.endsWith(DOT) && !key.startsWith(DOT)
+              && !key.startsWith(SAFE_REF) && !key.startsWith("[")
+              && !key.startsWith(SAFE_INDEX) && !key.startsWith(SPREAD_REF)) {
         return bodyRootPath + DOT + key
       }
       return bodyRootPath + key
diff --git a/rest-assured/src/main/groovy/io/restassured/internal/RestAssuredResponseOptionsGroovyImpl.groovy b/rest-assured/src/main/groovy/io/restassured/internal/RestAssuredResponseOptionsGroovyImpl.groovy
index dd5c313b..545af946 100644
--- a/rest-assured/src/main/groovy/io/restassured/internal/RestAssuredResponseOptionsGroovyImpl.groovy
+++ b/rest-assured/src/main/groovy/io/restassured/internal/RestAssuredResponseOptionsGroovyImpl.groovy
@@ -18,7 +18,7 @@
 package io.restassured.internal
 
 import groovy.xml.StreamingMarkupBuilder
-import io.restassured.assertion.CookieMatcher
+import io.restassured.internal.assertion.CookieMatcher
 import io.restassured.common.mapper.DataToDeserialize
 import io.restassured.common.mapper.TypeRef
 import io.restassured.config.DecoderConfig
diff --git a/rest-assured/src/main/groovy/io/restassured/internal/SpecificationMerger.groovy b/rest-assured/src/main/groovy/io/restassured/internal/SpecificationMerger.groovy
index 8b632ae9..8478afb6 100644
--- a/rest-assured/src/main/groovy/io/restassured/internal/SpecificationMerger.groovy
+++ b/rest-assured/src/main/groovy/io/restassured/internal/SpecificationMerger.groovy
@@ -53,7 +53,8 @@ class SpecificationMerger {
     thisOne.contentType = with.contentType
     thisOne.rpr.defaultParser = with.rpr.defaultParser
     thisOne.rpr.additional.putAll(with.rpr.additional)
-    thisOne.bodyMatchers << with.bodyMatchers
+    thisOne.responseLogDetail = with.responseLogDetail
+    thisOne.bodyMatchers.add(with.bodyMatchers)
     thisOne.bodyRootPath = with.bodyRootPath
     thisOne.cookieAssertions.addAll(with.cookieAssertions)
     thisOne.expectedStatusCode = with.expectedStatusCode
diff --git a/rest-assured/src/main/groovy/io/restassured/internal/mapping/ObjectMapperDeserializationContextImpl.groovy b/rest-assured/src/main/groovy/io/restassured/internal/mapping/ObjectMapperDeserializationContextImpl.groovy
index a6c26825..a28648f9 100644
--- a/rest-assured/src/main/groovy/io/restassured/internal/mapping/ObjectMapperDeserializationContextImpl.groovy
+++ b/rest-assured/src/main/groovy/io/restassured/internal/mapping/ObjectMapperDeserializationContextImpl.groovy
@@ -17,7 +17,7 @@
 
 package io.restassured.internal.mapping
 
-import io.restassured.internal.common.mapper.ObjectDeserializationContextImpl
+import io.restassured.internal.common.ObjectDeserializationContextImpl
 import io.restassured.mapper.ObjectMapperDeserializationContext
 
 class ObjectMapperDeserializationContextImpl extends ObjectDeserializationContextImpl implements ObjectMapperDeserializationContext {
diff --git a/rest-assured/src/main/java/io/restassured/internal/assertion/BodyMatcher.java b/rest-assured/src/main/java/io/restassured/internal/assertion/BodyMatcher.java
new file mode 100644
index 00000000..84001f73
--- /dev/null
+++ b/rest-assured/src/main/java/io/restassured/internal/assertion/BodyMatcher.java
@@ -0,0 +1,192 @@
+package io.restassured.internal.assertion;
+
+import io.restassured.assertion.StreamVerifier;
+import io.restassured.config.RestAssuredConfig;
+import io.restassured.config.XmlConfig;
+import io.restassured.internal.ResponseParserRegistrar;
+import io.restassured.internal.common.assertion.Assertion;
+import io.restassured.response.Response;
+import org.apache.commons.lang3.StringUtils;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.StringDescription;
+import org.hamcrest.xml.HasXPath;
+import org.w3c.dom.Element;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.ByteArrayInputStream;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import static io.restassured.config.MatcherConfig.ErrorDescriptionType.REST_ASSURED;
+import static java.lang.String.format;
+
+public class BodyMatcher {
+
+    private static final String XPATH = "XPath";
+    private Object key;
+    private Matcher matcher;
+    private ResponseParserRegistrar rpr;
+
+    public Map<String, Object> validate(Response response, Object contentParser, RestAssuredConfig config) {
+        boolean success = true;
+        String errorMessage = "";
+
+        contentParser = fallbackToResponseBodyIfContentParserIsNull(response, contentParser);
+        if (key == null) {
+            if (isXPathMatcher()) {
+                XmlConfig xmlConfig = config.getXmlConfig();
+                boolean namespaceAware = xmlConfig.isNamespaceAware();
+                Map<String, Boolean> features = xmlConfig.features();
+
+                final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+                factory.setNamespaceAware(namespaceAware);
+                if (!features.isEmpty()) {
+                    features.forEach((featureName, isEnabled) -> {
+                        try {
+                            factory.setFeature(featureName, isEnabled);
+                        } catch (ParserConfigurationException e) {
+                            throw new RuntimeException(e);
+                        }
+                    });
+                }
+
+
+                Map<String, Object> properties = xmlConfig.properties();
+                if (!properties.isEmpty()) {
+                    properties.forEach(factory::setAttribute);
+                }
+
+
+                Element node;
+                try {
+                    node = factory.newDocumentBuilder().parse(new ByteArrayInputStream(response.asByteArray())).getDocumentElement();
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+
+                if (!matcher.matches(node)) {
+                    success = false;
+                    if (config.getMatcherConfig().hasErrorDescriptionType(REST_ASSURED)) {
+                        errorMessage = String.format("Expected: %s\n  Actual: %s\n", StringUtils.trim(matcher.toString()), contentParser);
+                    } else {
+                        errorMessage = getDescription(matcher, contentParser);
+                    }
+                }
+
+            } else if (!matcher.matches(response.asString())) {
+                success = false;
+                if (config.getMatcherConfig().hasErrorDescriptionType(REST_ASSURED)) {
+                    errorMessage = "Response body doesn't match expectation.\nExpected: " + getMatcher() + "\n  Actual: " + contentParser + "\n";
+                } else {
+                    errorMessage = String.format("Response body doesn't match expectation.\n%s", getDescription(matcher, response.asString()));
+                }
+
+            }
+
+        } else {
+            Assertion assertion = (Assertion) StreamVerifier.newAssertion(response, key, rpr);
+            Object result = null;
+            if (contentParser != null) {
+                if (contentParser instanceof String) {
+                    // This happens for example when expecting JSON/XML assertion but response content is empty
+                    boolean isEmpty = ((String) contentParser).isEmpty();
+                    errorMessage = format("Cannot assert that path \"%s\" matches %s because the response body %s.", key, matcher, isEmpty ? "is empty" : "equal to \"$contentParser\"");
+                    success = false;
+                } else {
+                    result = assertion.getResult(contentParser, config);
+                }
+            }
+
+            if (success && !matcher.matches(result)) {
+                success = false;
+                if (config.getMatcherConfig().hasErrorDescriptionType(REST_ASSURED)) {
+                    if (result instanceof Object[]) {
+                        result = Arrays.stream((Object[]) result).map(Objects::toString).collect(Collectors.joining(","));
+                    }
+                    errorMessage = String.format("%s %s doesn't match.\nExpected: %s\n  Actual: %s\n", assertion.description(), key, removeQuotesIfString(matcher.toString()), removeQuotesIfString(new StringDescription().appendValue(result).toString()));
+                } else {
+                    errorMessage = String.format("%s %s doesn't match.\n%s", assertion.description(), key, getDescription(matcher, result));
+                }
+
+            }
+
+        }
+
+        LinkedHashMap<String, Object> map = new LinkedHashMap<>(2);
+        map.put("success", success);
+        map.put("errorMessage", errorMessage);
+        return map;
+    }
+
+    private static String getDescription(Matcher matcher, Object actual) {
+        Description description = new StringDescription();
+        description.appendText("\nExpected: ").appendDescriptionOf(matcher).appendText("\n  Actual: ");
+        matcher.describeMismatch(actual, description);
+        return description.toString();
+    }
+
+    private static String removeQuotesIfString(String string) {
+        if (StringUtils.startsWith(string, "\"") && StringUtils.endsWith(string, "\"")) {
+            String start = StringUtils.removeStart(string, "\"");
+            string = StringUtils.removeEnd(start, "\"");
+        }
+
+        return string;
+    }
+
+    public static Object fallbackToResponseBodyIfContentParserIsNull(Response response, Object contentParser) {
+        if (contentParser == null) {
+            return response.asString();
+        }
+
+        return contentParser;
+    }
+
+    private boolean isXPathMatcher() {
+        Supplier<Boolean> isNestedMatcherContainingXPathMatcher = () -> {
+            StringDescription description = new StringDescription();
+            getMatcher().describeTo(description);
+            return description.toString().contains(XPATH);
+        };
+
+        return matcher instanceof HasXPath || isNestedMatcherContainingXPathMatcher.get();
+    }
+
+    public boolean requiresTextParsing() {
+        return key == null || isXPathMatcher();
+    }
+
+    public boolean requiresPathParsing() {
+        return !requiresTextParsing();
+    }
+
+    public Object getKey() {
+        return key;
+    }
+
+    public void setKey(Object key) {
+        this.key = key;
+    }
+
+    public Matcher getMatcher() {
+        return matcher;
+    }
+
+    public void setMatcher(Matcher matcher) {
+        this.matcher = matcher;
+    }
+
+    public ResponseParserRegistrar getRpr() {
+        return rpr;
+    }
+
+    public void setRpr(ResponseParserRegistrar rpr) {
+        this.rpr = rpr;
+    }
+}
diff --git a/rest-assured/src/main/java/io/restassured/internal/assertion/BodyMatcherGroup.java b/rest-assured/src/main/java/io/restassured/internal/assertion/BodyMatcherGroup.java
new file mode 100644
index 00000000..9de6cdfa
--- /dev/null
+++ b/rest-assured/src/main/java/io/restassured/internal/assertion/BodyMatcherGroup.java
@@ -0,0 +1,45 @@
+package io.restassured.internal.assertion;
+
+import io.restassured.config.RestAssuredConfig;
+import io.restassured.response.Response;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class BodyMatcherGroup {
+    private final List<BodyMatcher> bodyAssertions = new ArrayList<>();
+
+    public void add(BodyMatcherGroup group) {
+        bodyAssertions.addAll(group.bodyAssertions);
+    }
+
+    public void add(BodyMatcher bodyMatcher) {
+        bodyAssertions.add(bodyMatcher);
+    }
+
+    public void reset() {
+        bodyAssertions.clear();
+    }
+
+    public int size() {
+        return bodyAssertions.size();
+    }
+
+    public List<Object> validate(final Response response, final Object contentParser, final RestAssuredConfig config) {
+        return bodyAssertions.stream().map(bodyMatcher -> bodyMatcher.validate(response, contentParser, config)).collect(Collectors.toList());
+
+    }
+
+    public boolean containsMatchers() {
+        return !bodyAssertions.isEmpty();
+    }
+
+    public boolean requiresTextParsing() {
+        return bodyAssertions.stream().anyMatch(BodyMatcher::requiresTextParsing);
+    }
+
+    public boolean requiresPathParsing() {
+        return bodyAssertions.stream().anyMatch(BodyMatcher::requiresPathParsing);
+    }
+}
diff --git a/rest-assured/src/main/java/io/restassured/internal/assertion/CookieMatcher.java b/rest-assured/src/main/java/io/restassured/internal/assertion/CookieMatcher.java
new file mode 100644
index 00000000..510d0a44
--- /dev/null
+++ b/rest-assured/src/main/java/io/restassured/internal/assertion/CookieMatcher.java
@@ -0,0 +1,172 @@
+package io.restassured.internal.assertion;
+
+import io.restassured.http.Cookie;
+import io.restassured.http.Cookies;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.client.utils.DateUtils;
+import org.hamcrest.Matcher;
+import org.hamcrest.StringDescription;
+
+import java.util.*;
+
+import static io.restassured.http.Cookie.*;
+
+public class CookieMatcher {
+    private static final Log log = LogFactory.getLog(CookieMatcher.class);
+    private Object cookieName;
+    private Matcher<String> matcher;
+
+    public Map<String, Object> validateCookies(List<String> headerWithCookieList, Cookies responseCookies) {
+        boolean success = true;
+        String errorMessage = "";
+        if (headerWithCookieList == null || (headerWithCookieList.isEmpty() && !responseCookies.exist())) {
+            success = false;
+            errorMessage = "No cookies defined in the response\n";
+        } else {
+            Cookies cookiesInHeader = getCookies(headerWithCookieList);
+            List<Cookie> mergedCookies = new ArrayList<>();
+            cookiesInHeader.forEach(mergedCookies::add);
+            for (Cookie responseCookie : (responseCookies == null ? new Cookies() : responseCookies)) {
+                if (!cookiesInHeader.hasCookieWithName(responseCookie.getName())) {
+                    mergedCookies.add(responseCookie);
+                }
+            }
+
+
+            Cookies raCookies = new Cookies(mergedCookies);
+            Cookie cookie = raCookies.get((String) cookieName);
+            if (cookie == null) {
+                String cookiesAsString = raCookies.toString();
+                success = false;
+                errorMessage = "Cookie \"" + getCookieName() + "\" was not defined in the response. Cookies are: \n" + cookiesAsString + "\n";
+            } else {
+                String value = cookie.getValue();
+                if (!matcher.matches(value)) {
+                    success = false;
+                    String expectedDescription = getExpectedDescription(matcher);
+                    String mismatchDescription = getMismatchDescription(matcher, value);
+                    errorMessage = "Expected cookie \"" + getCookieName() + "\" was not " + expectedDescription + ", " + mismatchDescription + ".\n";
+                }
+            }
+        }
+
+        Map<String, Object> map = new LinkedHashMap<>(2);
+        map.put("success", success);
+        map.put("errorMessage", errorMessage);
+        return map;
+    }
+
+    public static String getExpectedDescription(Matcher<?> matcher) {
+        StringDescription expectedDescription = new StringDescription();
+        matcher.describeTo(expectedDescription);
+        return expectedDescription.toString();
+    }
+
+    public static String getMismatchDescription(Matcher<?> matcher, Object value) {
+        StringDescription mismatchDescription = new StringDescription();
+        matcher.describeMismatch(value, mismatchDescription);
+        return mismatchDescription.toString();
+    }
+
+    public static Cookies getCookies(List<String> headerWithCookieList) {
+        List<Cookie> cookieList = new ArrayList<>();
+        headerWithCookieList.forEach(it -> {
+            String[] cookieStrings = StringUtils.split(it, ";");
+            Cookie.Builder cookieBuilder = null;
+            for (int i = 0; i < cookieStrings.length; i++) {
+                String part = cookieStrings[i];
+                if (i == 0) {
+                    if (part.contains("=")) {
+                        final Iterator<String> iterator = getKeyAndValueOfCookie(part).iterator();
+                        String cookieKey = iterator.hasNext() ? iterator.next() : null;
+                        String cookieValue = iterator.hasNext() ? iterator.next() : null;
+                        cookieBuilder = new Cookie.Builder(cookieKey, cookieValue);
+                    } else {
+                        cookieBuilder = new Cookie.Builder(part, null);
+                    }
+                } else if (part.contains("=")) {
+                    final Iterator<String> iterator = getKeyAndValueOfCookie(part).iterator();
+                    String cookieKey = iterator.hasNext() ? iterator.next() : null;
+                    String cookieValue = iterator.hasNext() ? iterator.next() : null;
+                    setCookieProperty(cookieBuilder, cookieKey, cookieValue);
+                } else {
+                    setCookieProperty(cookieBuilder, part, null);
+                }
+            }
+            cookieList.add(cookieBuilder == null ? null : cookieBuilder.build());
+        });
+        return new Cookies(cookieList);
+    }
+
+
+    public static List<String> getKeyAndValueOfCookie(String part) {
+        int indexOfEqual = StringUtils.indexOf(part, "=");
+        String cookieKey;
+        String cookieValue;
+        if (indexOfEqual > -1) {
+            cookieKey = StringUtils.substring(part, 0, indexOfEqual);
+            cookieValue = StringUtils.substring(part, indexOfEqual + 1);
+        } else {
+            cookieKey = part;
+            cookieValue = null;
+        }
+
+        return new ArrayList<>(Arrays.asList(StringUtils.trim(cookieKey), StringUtils.trim(cookieValue)));
+    }
+
+    private static void setCookieProperty(Cookie.Builder builder, String name, String value) {
+        name = StringUtils.trim(name);
+        if (value != null || StringUtils.equalsIgnoreCase(name, SECURE) || StringUtils.equalsIgnoreCase(name, HTTP_ONLY)) {
+            if (StringUtils.equalsIgnoreCase(name, COMMENT)) {
+                builder.setComment(value);
+            } else if (StringUtils.equalsIgnoreCase(name, VERSION)) {
+                // Some servers supply the version in quotes, remove them
+                value = StringUtils.trim(StringUtils.remove(value, "\""));
+                if (NumberUtils.isDigits(value)) {
+                    builder.setVersion(Integer.parseInt(value));
+                }
+            } else if (StringUtils.equalsIgnoreCase(name, PATH)) {
+                builder.setPath(value);
+            } else if (StringUtils.equalsIgnoreCase(name, DOMAIN)) {
+                builder.setDomain(value);
+            } else if (StringUtils.equalsIgnoreCase(name, MAX_AGE)) {
+                builder.setMaxAge(Long.parseLong(value));
+            } else if (StringUtils.equalsIgnoreCase(name, SECURE)) {
+                builder.setSecured(true);
+            } else if (StringUtils.equalsIgnoreCase(name, HTTP_ONLY)) {
+                builder.setHttpOnly(true);
+            } else if (StringUtils.equalsIgnoreCase(name, EXPIRES)) {
+                value = StringUtils.trim(StringUtils.remove(value, "\""));
+                Date parsedDate = DateUtils.parseDate(value);
+                if (parsedDate != null) {
+                    builder.setExpiryDate(parsedDate);
+                } else {
+                    log.warn("Ignoring unparsable 'Expires' attribute value: " + value);
+                }
+
+            } else if (StringUtils.equalsIgnoreCase(name, SAME_SITE)) {
+                builder.setSameSite(value);
+            }
+
+        }
+    }
+
+    public Object getCookieName() {
+        return cookieName;
+    }
+
+    public void setCookieName(Object cookieName) {
+        this.cookieName = cookieName;
+    }
+
+    public Matcher<String> getMatcher() {
+        return matcher;
+    }
+
+    public void setMatcher(Matcher<String> matcher) {
+        this.matcher = matcher;
+    }
+}
diff --git a/xml-path/pom.xml b/xml-path/pom.xml
index 1574bb43..906910a2 100644
--- a/xml-path/pom.xml
+++ b/xml-path/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>rest-assured-parent</artifactId>
         <groupId>io.rest-assured</groupId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -43,6 +43,7 @@
                             io.restassured.internal.path.xml.*
                         </Export-Package>
                         <Import-Package>
+                            groovy.*;version="${groovy.range}",
                             org.apache.groovy.*;version="${groovy.range}",
                             *
                         </Import-Package>
@@ -69,7 +70,7 @@
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>rest-assured-common</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
diff --git a/xml-path/src/main/groovy/io/restassured/internal/path/xml/mapping/XmlObjectDeserializer.groovy b/xml-path/src/main/groovy/io/restassured/internal/path/xml/mapping/XmlObjectDeserializer.groovy
index 7905f98c..b6d0a67b 100644
--- a/xml-path/src/main/groovy/io/restassured/internal/path/xml/mapping/XmlObjectDeserializer.groovy
+++ b/xml-path/src/main/groovy/io/restassured/internal/path/xml/mapping/XmlObjectDeserializer.groovy
@@ -18,7 +18,7 @@ package io.restassured.internal.path.xml.mapping
 
 import io.restassured.common.mapper.DataToDeserialize
 import io.restassured.common.mapper.ObjectDeserializationContext
-import io.restassured.internal.common.mapper.ObjectDeserializationContextImpl
+import io.restassured.internal.common.ObjectDeserializationContextImpl
 import io.restassured.path.xml.config.XmlParserType
 import io.restassured.path.xml.config.XmlPathConfig
 import io.restassured.path.xml.mapper.factory.JAXBObjectMapperFactory


Test patch:
diff --git a/dist/dist-spring-web-test-client/pom.xml b/dist/dist-spring-web-test-client/pom.xml
index d07a37f1..376b7eeb 100644
--- a/dist/dist-spring-web-test-client/pom.xml
+++ b/dist/dist-spring-web-test-client/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <artifactId>dist</artifactId>
         <groupId>io.rest-assured</groupId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/examples/rest-assured-itest-java-osgi/pom.xml b/examples/rest-assured-itest-java-osgi/pom.xml
index a922ab85..1b33d971 100644
--- a/examples/rest-assured-itest-java-osgi/pom.xml
+++ b/examples/rest-assured-itest-java-osgi/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <groupId>io.rest-assured.examples</groupId>
         <artifactId>example-parent</artifactId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
diff --git a/examples/rest-assured-itest-java/pom.xml b/examples/rest-assured-itest-java/pom.xml
index 3e8055e3..bd87a612 100644
--- a/examples/rest-assured-itest-java/pom.xml
+++ b/examples/rest-assured-itest-java/pom.xml
@@ -19,7 +19,7 @@
     <parent>
         <groupId>io.rest-assured.examples</groupId>
         <artifactId>example-parent</artifactId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -50,23 +50,23 @@
         <dependency>
             <groupId>io.rest-assured.examples</groupId>
             <artifactId>scalatra-webapp</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
             <type>war</type>
         </dependency>
         <dependency>
             <groupId>io.rest-assured.examples</groupId>
             <artifactId>scalatra-example</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>rest-assured</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>io.rest-assured</groupId>
             <artifactId>json-schema-validator</artifactId>
-            <version>5.0.1-SNAPSHOT</version>
+            <version>5.1.1-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.eclipse.jetty</groupId>
@@ -105,7 +105,7 @@
         <dependency>
             <groupId>com.google.code.gson</groupId>
             <artifactId>gson</artifactId>
-            <version>2.2.4</version>
+            <version>2.9.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/examples/rest-assured-itest-java/src/test/java/io/restassured/itest/java/CustomObjectMappingITest.java b/examples/rest-assured-itest-java/src/test/java/io/restassured/itest/java/CustomObjectMappingITest.java
index bc4d7c36..a3b4b627 100644
--- a/examples/rest-assured-itest-java/src/test/java/io/restassured/itest/java/CustomObjectMappingITest.java
+++ b/examples/rest-assured-itest-java/src/test/java/io/restassured/itest/java/CustomObjectMappingITest.java
@@ -47,13 +47,13 @@ public class CustomObjectMappingITest extends WithJetty {
     public AtomicBoolean customDeserializationUsed = new AtomicBoolean(false);
 
     @Before public void
-    setup() throws Exception {
+    setup() {
         customSerializationUsed.set(false);
         customDeserializationUsed.set(false);
     }
 
     @Test public void
-    using_explicit_custom_object_mapper() throws Exception {
+    using_explicit_custom_object_mapper() {
         final Message message = new Message();
         message.setMessage("A message");
         final ObjectMapper mapper = new ObjectMapper() {
diff --git a/examples/rest-assured-itest-java/src/test/java/io/restassured/itest/java/RootPathITest.java b/examples/rest-assured-itest-java/src/test/java/io/restassured/itest/java/RootPathITest.java
index 94e333f7..20a2f449 100644
--- a/examples/rest-assured-itest-java/src/test/java/io/restassured/itest/java/RootPathITest.java
+++ b/examples/rest-assured-itest-java/src/test/java/io/restassured/itest/java/RootPathITest.java
@@ -70,6 +70,26 @@ public class RootPathITest extends WithJetty {
                  get("/jsonStore");
     }
 
+    @Test
+    public void specifyingRootPathThatDoesntEndWithDotAndBodyThatStartsWithSafeRefWorks() {
+        expect().
+                rootPath("store.book").
+                body("?.category.size()", equalTo(4)).
+                body("?.author.size()", equalTo(4)).
+                when().
+                get("/jsonStore");
+    }
+
+    @Test
+    public void specifyingRootPathThatDoesntEndWithDotAndBodyThatStartsWithSpreadWorks() {
+        expect().
+                rootPath("store.book").
+                body("*.category.size()", equalTo(4)).
+                body("*.author.size()", equalTo(4)).
+                when().
+                get("/jsonStore");
+    }
+
     @Test
     public void specifyingRootPathAndBodyThatStartsWithArrayIndexingWorks() {
         expect().
@@ -79,6 +99,15 @@ public class RootPathITest extends WithJetty {
                 get("/jsonStore");
     }
 
+    @Test
+    public void specifyingRootPathAndBodyThatStartsWithSafeArrayIndexingWorks() {
+        expect().
+                rootPath("store.book").
+                body("?[0].category", either(equalTo("reference")).or(equalTo("fiction"))).
+                when().
+                get("/jsonStore");
+    }
+
     @Test
     public void specifyingRootPathThatAndEmptyPathWorks() {
         expect().
diff --git a/json-path/src/test/java/io/restassured/path/json/JsonPathTest.java b/json-path/src/test/java/io/restassured/path/json/JsonPathTest.java
index 2bc73bdc..1f706e0d 100644
--- a/json-path/src/test/java/io/restassured/path/json/JsonPathTest.java
+++ b/json-path/src/test/java/io/restassured/path/json/JsonPathTest.java
@@ -789,4 +789,18 @@ public class JsonPathTest {
         // Then
         assertThat(jsonPath.getString("some-list[0]"), equalTo("one"));
     }
+
+
+    /** Tests #1543 is fixed    */
+    @Test public void
+    does_not_fail_on_absent_lists() {
+        // Given
+        String json = "{ \"root\" : { }";
+
+        // When
+        JsonPath jsonPath = JsonPath.from(json);
+
+        // Then   no exception should be thrown
+        assertThat(jsonPath.getString("root.items[0]"), is(nullValue()));
+    }
 }
diff --git a/modules/spring-web-test-client/pom.xml b/modules/spring-web-test-client/pom.xml
index 92290030..f0b37a7b 100644
--- a/modules/spring-web-test-client/pom.xml
+++ b/modules/spring-web-test-client/pom.xml
@@ -20,10 +20,10 @@
     <parent>
         <groupId>io.rest-assured</groupId>
         <artifactId>modules</artifactId>
-        <version>5.0.1-SNAPSHOT</version>
+        <version>5.1.1-SNAPSHOT</version>
     </parent>
     <artifactId>spring-web-test-client</artifactId>
-    <version>5.0.1-SNAPSHOT</version>
+    <version>5.1.1-SNAPSHOT</version>
     <name>spring-web-test-client</name>
     <url>http://maven.apache.org</url>
     <properties>
@@ -72,6 +72,7 @@
             <groupId>org.springframework</groupId>
             <artifactId>spring-test</artifactId>
             <version>${spring.version}</version>
+            <optional>true</optional>
         </dependency>
         <dependency>
             <groupId>org.springframework</groupId>
@@ -82,6 +83,7 @@
             <groupId>org.springframework</groupId>
             <artifactId>spring-webflux</artifactId>
             <version>${spring.version}</version>
+            <optional>true</optional>
         </dependency>
         
         <dependency>
diff --git a/modules/spring-web-test-client/src/main/java/io/restassured/module/webtestclient/internal/ExchangeResultConverter.java b/modules/spring-web-test-client/src/main/java/io/restassured/module/webtestclient/internal/ExchangeResultConverter.java
index 40db8855..c21b9ed8 100644
--- a/modules/spring-web-test-client/src/main/java/io/restassured/module/webtestclient/internal/ExchangeResultConverter.java
+++ b/modules/spring-web-test-client/src/main/java/io/restassured/module/webtestclient/internal/ExchangeResultConverter.java
@@ -55,17 +55,18 @@ class ExchangeResultConverter {
                 logRepository);
         restAssuredResponse.setConfig(ConfigConverter.convertToRestAssuredConfig(config));
         byte[] responseBodyContent = ofNullable(consumer).map(theConsumer ->
-                responseSpec.expectBody().consumeWith(theConsumer).returnResult().getResponseBodyContent())
+                        responseSpec.expectBody().consumeWith(theConsumer).returnResult().getResponseBodyContent())
                 .orElseGet(() -> responseSpec.expectBody().returnResult().getResponseBodyContent());
         restAssuredResponse.setContent(responseBodyContent);
         MediaType contentType = result.getResponseHeaders().getContentType();
         restAssuredResponse.setContentType(ofNullable(contentType).map(MimeType::toString).orElse(null));
         restAssuredResponse.setHasExpectations(false);
-        restAssuredResponse.setStatusCode(result.getStatus().value());
+        HttpStatus status = getHttpStatusByReflection(result);
+        restAssuredResponse.setStatusCode(status.value());
         List<Header> responseHeaders = assembleHeaders(result.getResponseHeaders());
         restAssuredResponse.setResponseHeaders(new Headers(responseHeaders));
         restAssuredResponse.setRpr(responseParserRegistrar);
-        restAssuredResponse.setStatusLine(buildResultString(result.getStatus()));
+        restAssuredResponse.setStatusLine(buildResultString(status));
         restAssuredResponse.setFilterContextProperties(new HashMap<Object, Object>() {{
             put(TimingFilter.RESPONSE_TIME_MILLISECONDS, responseTime);
         }});
@@ -98,4 +99,15 @@ class ExchangeResultConverter {
         return new Cookies(cookies);
     }
 
+    // Due to incompatibilities between Spring 5 and 6
+    private static HttpStatus getHttpStatusByReflection(FluxExchangeResult<byte[]> result) {
+        final HttpStatus status;
+        try {
+            status = (HttpStatus) result.getClass().getMethod("getStatus").invoke(result);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return status;
+    }
+
 }
diff --git a/rest-assured/src/test/groovy/io/restassured/SpecificationMergerTest.groovy b/rest-assured/src/test/groovy/io/restassured/SpecificationMergerTest.groovy
index 2a937cc3..ed2e7494 100644
--- a/rest-assured/src/test/groovy/io/restassured/SpecificationMergerTest.groovy
+++ b/rest-assured/src/test/groovy/io/restassured/SpecificationMergerTest.groovy
@@ -22,6 +22,7 @@ import io.restassured.builder.ResponseSpecBuilder
 import io.restassured.config.RestAssuredConfig
 import io.restassured.filter.Filter
 import io.restassured.filter.FilterContext
+import io.restassured.filter.log.LogDetail
 import io.restassured.http.ContentType
 import io.restassured.internal.SpecificationMerger
 import io.restassured.internal.filter.FormAuthFilter
@@ -176,6 +177,16 @@ class SpecificationMergerTest {
     assertEquals Parser.JSON, merge.rpr.defaultParser
   }
 
+  @Test
+  void overwritesLogDetail() throws Exception {
+    def merge = new ResponseSpecBuilder().log(LogDetail.COOKIES).build()
+    def with = new ResponseSpecBuilder().log(LogDetail.URI).build()
+
+    SpecificationMerger.merge(merge, with)
+
+    assertEquals LogDetail.URI, merge.getLogDetail()
+  }
+
   @Test
   void authFiltersAreOverwritten() throws Exception {
     def merge = new RequestSpecBuilder().addFilter(new FormAuthFilter()).build()
diff --git a/rest-assured/src/test/java/io/restassured/assertion/BodyMatcherTest.java b/rest-assured/src/test/java/io/restassured/assertion/BodyMatcherTest.java
index a21b7f6d..88257412 100644
--- a/rest-assured/src/test/java/io/restassured/assertion/BodyMatcherTest.java
+++ b/rest-assured/src/test/java/io/restassured/assertion/BodyMatcherTest.java
@@ -6,6 +6,7 @@ import io.restassured.config.RestAssuredConfig;
 import io.restassured.http.ContentType;
 import io.restassured.internal.ContentParser;
 import io.restassured.internal.ResponseParserRegistrar;
+import io.restassured.internal.assertion.BodyMatcher;
 import io.restassured.response.Response;
 import org.hamcrest.core.IsEqual;
 import org.junit.Test;
@@ -81,7 +82,7 @@ public class BodyMatcherTest {
                 .setContentType(ContentType.JSON)
                 .build();
 
-        final Map<String, Object> result = (Map<String, Object>) bodyMatcher.validate(response,
+        final Map<String, Object> result = bodyMatcher.validate(response,
                 new ContentParser().parse(response, responseParserRegistrar, config, true),
                 config);
 
diff --git a/rest-assured/src/test/java/io/restassured/assertion/CookieMatcherMessagesTest.java b/rest-assured/src/test/java/io/restassured/assertion/CookieMatcherMessagesTest.java
index d81696ec..bfb1f15e 100644
--- a/rest-assured/src/test/java/io/restassured/assertion/CookieMatcherMessagesTest.java
+++ b/rest-assured/src/test/java/io/restassured/assertion/CookieMatcherMessagesTest.java
@@ -17,6 +17,7 @@
 package io.restassured.assertion;
 
 import io.restassured.http.Cookies;
+import io.restassured.internal.assertion.CookieMatcher;
 import org.hamcrest.Description;
 import org.hamcrest.Matchers;
 import org.hamcrest.TypeSafeDiagnosingMatcher;
@@ -34,7 +35,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
 @SuppressWarnings("unchecked")
 public class CookieMatcherMessagesTest {
 
-    private String[] cookies = new String[]{"DEVICE_ID=123; Domain=.test.com; Expires=Thu, 12-Oct-2023 09:34:31 GMT; Path=/; Secure; HttpOnly;"};
+    private final String[] cookies = new String[]{"DEVICE_ID=123; Domain=.test.com; Expires=Thu, 12-Oct-2023 09:34:31 GMT; Path=/; Secure; HttpOnly;"};
 
     @Test
     public void shouldPrintValidErrorMessageForStandardMatchers() {
@@ -43,7 +44,7 @@ public class CookieMatcherMessagesTest {
         cookieMatcher.setCookieName("DEVICE_ID");
         cookieMatcher.setMatcher(Matchers.containsString("X"));
 
-        Map<String, Object> result = (Map<String, Object>) cookieMatcher.validateCookies(Arrays.asList(cookies), new Cookies());
+        Map<String, Object> result = cookieMatcher.validateCookies(Arrays.asList(cookies), new Cookies());
         assertThat((Boolean)result.get("success"), equalTo(false));
         assertThat(result.get("errorMessage").toString(), equalTo("Expected cookie \"DEVICE_ID\" was not a string containing \"X\", was \"123\".\n"));
     }
@@ -55,7 +56,7 @@ public class CookieMatcherMessagesTest {
         cookieMatcher.setCookieName("DEVICE_ID");
         cookieMatcher.setMatcher(new ContainsXMatcher());
 
-        Map<String, Object> result = (Map<String, Object>) cookieMatcher.validateCookies(Arrays.asList(cookies), new Cookies());
+        Map<String, Object> result = cookieMatcher.validateCookies(Arrays.asList(cookies), new Cookies());
         assertThat((Boolean)result.get("success"), equalTo(false));
         assertThat(result.get("errorMessage").toString(), equalTo("Expected cookie \"DEVICE_ID\" was not containing 'X', \"123\" not containing 'X'.\n"));
     }
diff --git a/rest-assured/src/test/java/io/restassured/assertion/CookieMatcherTest.java b/rest-assured/src/test/java/io/restassured/assertion/CookieMatcherTest.java
index ff8b24d2..aa791e7d 100644
--- a/rest-assured/src/test/java/io/restassured/assertion/CookieMatcherTest.java
+++ b/rest-assured/src/test/java/io/restassured/assertion/CookieMatcherTest.java
@@ -18,11 +18,14 @@ package io.restassured.assertion;
 
 import io.restassured.http.Cookie;
 import io.restassured.http.Cookies;
+import io.restassured.internal.assertion.CookieMatcher;
 import org.hamcrest.Matchers;
 import org.junit.Test;
 
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -36,11 +39,11 @@ import static org.junit.Assert.assertEquals;
 public class CookieMatcherTest {
     @Test
     public void testSetVersion() throws ParseException {
-        String[] cookies = new String[]{
+        List<String> cookies = Arrays.asList(
                 "DEVICE_ID=123; Domain=.test.com; Expires=Thu, 12-Oct-2023 09:34:31 GMT; Path=/; Secure; HttpOnly; SameSite=Lax",
                 "SPRING_SECURITY_REMEMBER_ME_COOKIE=12345;Version=0;Domain=.test.com;Path=/;Max-Age=1209600",
                 "COOKIE_WITH_ZERO_MAX_AGE=1234;Version=0;Domain=.test.com;Path=/;Max-Age=0",
-                "COOKIE_WITH_NEGATIVE_MAX_AGE=123456;Version=0;Domain=.test.com;Path=/;Max-Age=-1"};
+                "COOKIE_WITH_NEGATIVE_MAX_AGE=123456;Version=0;Domain=.test.com;Path=/;Max-Age=-1");
 
         Cookies result = CookieMatcher.getCookies(cookies);
         assertEquals(4, result.size());
@@ -81,20 +84,18 @@ public class CookieMatcherTest {
         assertEquals(true, deviceCookie.isSecured());
         assertEquals(true, deviceCookie.isHttpOnly());
         assertEquals("Lax", deviceCookie.getSameSite());
-
     }
 
     @Test public void
     deals_with_empty_cookie_values() {
         // Given
-        String[] cookiesAsString = new String[]{
-                "un=bob; domain=bob.com; path=/", "", "_session_id=asdfwerwersdfwere; domain=bob.com; path=/; HttpOnly"};
+        List<String> cookiesAsString = Arrays.asList("un=bob; domain=bob.com; path=/", "", "_session_id=asdfwerwersdfwere; domain=bob.com; path=/; HttpOnly");
 
         // When
         Cookies cookies = CookieMatcher.getCookies(cookiesAsString);
 
         // Then
         assertThat(cookies.size(), is(3));
-        assertThat(cookies, Matchers.<Cookie>hasItem(nullValue()));
+        assertThat(cookies, Matchers.hasItem(nullValue()));
     }
 }