To send a REST-assured XML request, we set the content type to application/xml with an explicit charset and pass the XML document to body(), either as a String, a file or a Java object. REST-assured sends the XML as the request body and reads an XML response with the same GPath syntax it uses for JSON.
We need XML requests for SOAP services, payment and banking gateways, travel and booking systems, and other enterprise APIs that never moved to JSON. The test code stays the same as for JSON APIs, except for the content type, the charset and the way we check the response.
The following example posts a hotel booking as XML and checks the XML response.
@Test
void createsBookingFromXml() {
given()
.contentType("application/xml; charset=UTF-8") // XML body, UTF-8 bytes
.body("""
<booking>
<guest>Lokesh</guest>
<nights>3</nights>
</booking>
""")
.when()
.post("/bookings") // POST /api/bookings
.then()
.statusCode(201) // passes, 201 Created
.body("booking.@id", equalTo("42")) // passes, attribute id
.body("booking.status", equalTo("CONFIRMED")); // passes, element text
}
Notice that the paths start with the root element booking, and an attribute gets the @ prefix. We cover XML from strings, files and Java objects, the charset trap, response checks with GPath, XPath and XmlPath, namespaces and SOAP calls.
1. A Booking API That Accepts XML
Say a hotel chain sends bookings to a partner system that only accepts XML. Our tests check that the XML we send has the right elements and that we read the confirmation correctly. A WireMock stub plays the partner API, and its XPath matchers accept a booking only when the XML contains a guest element.
class BookingApiStubs {
static void register() {
String confirmed = "<booking id=\"42\"><guest>Lokesh</guest><nights>3</nights>"
+ "<status>CONFIRMED</status></booking>";
WireMock.stubFor(WireMock.post("/api/bookings")
.withHeader("Content-Type", WireMock.containing("application/xml"))
.withRequestBody(WireMock.matchingXPath("/booking/guest[text()='Lokesh']"))
.willReturn(WireMock.aResponse().withStatus(201)
.withHeader("Content-Type", "application/xml")
.withHeader("Location", "/api/bookings/42")
.withBody(confirmed)));
WireMock.stubFor(WireMock.post("/api/bookings")
.withRequestBody(WireMock.matchingXPath("/booking[not(guest)]"))
.willReturn(WireMock.aResponse().withStatus(400)
.withHeader("Content-Type", "application/xml")
.withBody("<error><message>guest is required</message></error>")));
WireMock.stubFor(WireMock.post("/api/bookings/intl")
.withHeader("Content-Type", WireMock.containing("charset=UTF-8"))
.withRequestBody(WireMock.matchingXPath("/booking/guest[text()='Zo\u00eb']"))
.willReturn(WireMock.aResponse().withStatus(201)));
WireMock.stubFor(WireMock.post("/ws/bookings")
.withHeader("SOAPAction", WireMock.equalTo("getBooking"))
.willReturn(WireMock.aResponse().withStatus(200)
.withHeader("Content-Type", "text/xml; charset=UTF-8")
.withBody("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<soap:Body><b:booking xmlns:b=\"urn:bookings\" id=\"42\">"
+ "<b:guest>Lokesh</b:guest></b:booking></soap:Body></soap:Envelope>")));
}
}
Before each test, BookingApiTest registers the stubs and copies the WireMock port into RestAssured.port, a setup taken from the REST-assured tutorial. The XML tests that follow are methods of BookingApiTest.
@WireMockTest
class BookingApiTest {
@BeforeEach
void pointRestAssuredAtStub(WireMockRuntimeInfo wireMock) {
RestAssured.port = wireMock.getHttpPort();
RestAssured.basePath = "/api";
BookingApiStubs.register();
}
@Test
void rejectsBookingWithoutGuest() {
given()
.contentType("application/xml; charset=UTF-8")
.body("<booking><nights>3</nights></booking>")
.when()
.post("/bookings")
.then()
.statusCode(400)
.body("error.message", equalTo("guest is required"));
}
}
The examples use REST-assured 6.1.0, JUnit 6.1.3, WireMock 3.13.2 and Java 25. For Java objects, we add Jakarta XML Binding 4 (jakarta.xml.bind-api 4.0.5 and org.glassfish.jaxb:jaxb-runtime 4.0.9) or Jackson 3 XML (tools.jackson.dataformat:jackson-dataformat-xml 3.2.3), as section 4 shows.
2. Sending XML as a String
A text block is the most direct way to send XML, because the test shows the exact document the server gets. It fits fixed payloads and negative tests, such as a missing element or an element in the wrong order.

We pass the content type as a full string, because ContentType.XML alone leads to the charset problem from the next section. REST-assured also accepts text/xml or a vendor type such as application/vnd.hotel+xml, whichever the API documents.
3. Setting the Charset for XML Bodies
When the content type has no charset, REST-assured adds charset=ISO-8859-1 for XML and encodes a String body with it. JSON bodies go out as UTF-8, so the problem shows up only with XML or plain text, and only when the data has characters outside ASCII, such as a guest name with an accented letter.
A server that honors the header decodes the bytes correctly. Many XML parsers, though, read the raw bytes, find no encoding declaration and use UTF-8, which is the XML default. A single ISO-8859-1 byte for an accented letter is not valid UTF-8, so the parser fails.
byte[] latin1 = "<guest>Zo\u00eb</guest>".getBytes(StandardCharsets.ISO_8859_1);
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(latin1)); // SAXParseException: Invalid byte 2 of 3-byte UTF-8 sequence.
Either set the charset in each request, as the examples here do, or change the default once for all XML requests through EncoderConfig.
@Test
void sendsAccentedNameAsUtf8() {
RestAssured.config = RestAssuredConfig.config()
.encoderConfig(encoderConfig().defaultCharsetForContentType("UTF-8", ContentType.XML));
given()
.contentType(ContentType.XML) // application/xml; charset=UTF-8
.body("<booking><guest>Zo\u00eb</guest><nights>2</nights></booking>")
.when()
.post("/bookings/intl")
.then()
.statusCode(201);
RestAssured.reset();
}
The call RestAssured.reset() at the end restores all static defaults, including the port, so in a real suite we set the config in @BeforeAll and leave it there. The same charset rule applies to a text/xml SOAP body.
4. Converting a Java Object to XML
Building XML strings by hand gets error-prone once a payload has more than a few elements. A Java class that maps to the XML keeps the test data typed, and there are two common ways to get the XML from it.
4.1. Letting REST-assured Serialize with Jakarta XML Binding
When the content type is XML and the body is an object, REST-assured serializes it with Jakarta XML Binding (formerly JAXB). The class needs @XmlRootElement and a no-argument constructor, which rules out records.
@XmlRootElement(name = "booking")
@XmlAccessorType(XmlAccessType.FIELD)
public class Booking {
String guest;
int nights;
public Booking() {
}
public Booking(String guest, int nights) {
this.guest = guest;
this.nights = nights;
}
}
@Test
void createsBookingFromJaxbObject() {
given()
.contentType("application/xml; charset=UTF-8")
.body(new Booking("Lokesh", 3)) // <booking><guest>Lokesh</guest>...
.when()
.post("/bookings")
.then()
.statusCode(201)
.header("Location", endsWith("/bookings/42"));
}
Jakarta XML Binding is no longer part of the JDK since Java 11, so the API and the runtime both go into the test dependencies. The annotations come from jakarta.xml.bind.annotation. REST-assured tries Jakarta XML Binding first and falls back to the older javax.xml.bind API only when the Jakarta classes are missing. More mapping options are in our guide to the JAXB Marshaller.
4.2. Converting with Jackson XmlMapper
Teams that already use Jackson for JSON can produce the XML with Jackson’s XmlMapper and send the resulting String. Records work here, and the root element name comes from @JsonRootName in Jackson 3, which replaces the deprecated @JacksonXmlRootElement.
@JsonRootName("booking")
record BookingRequest(String guest, int nights) {}
String xml = new XmlMapper().writeValueAsString(new BookingRequest("Lokesh", 3)); // "<booking><guest>Lokesh</guest><nights>3</nights></booking>"
@Test
void createsBookingFromJacksonXml() {
String xml = new XmlMapper().writeValueAsString(new BookingRequest("Lokesh", 3));
given()
.contentType("application/xml; charset=UTF-8")
.body(xml)
.when()
.post("/bookings")
.then()
.statusCode(201);
}
The difference matters for attributes and namespaces. Jakarta XML Binding uses @XmlAttribute and @XmlElement(namespace = …), while Jackson uses @JacksonXmlProperty(isAttribute = true). More examples are in Jackson XML conversions.
5. Reading the XML Body from a File
Large SOAP envelopes and sample messages from a partner’s documentation are easier to keep as files in src/test/resources. REST-assured streams the file bytes unchanged, so the encoding declaration inside the file must match the charset in the content type.
@Test
void createsBookingFromFile(@TempDir Path dir) throws IOException {
Path file = dir.resolve("booking.xml");
Files.writeString(file, """
<?xml version="1.0" encoding="UTF-8"?>
<booking><guest>Lokesh</guest><nights>3</nights></booking>
""", StandardCharsets.UTF_8);
given()
.contentType("application/xml; charset=UTF-8")
.body(file.toFile())
.when()
.post("/bookings")
.then()
.statusCode(201);
}
In a real project, we read the file from the classpath with getClass().getResourceAsStream(“/requests/booking.xml”) and pass the InputStream to body(). A File or a byte[] body is sent as is, so REST-assured does not re-encode it.
6. Validating the XML Response
REST-assured detects an XML response from its content type and evaluates body() paths with GPath, the syntax of Groovy’s XmlSlurper. The path starts at the root element, child elements are separated by dots, and attributes start with @. The XmlPath class runs the same expressions on a String.
XmlPath xml = XmlPath.from("<booking id=\"42\"><guest>Lokesh</guest><nights>3</nights><status>CONFIRMED</status></booking>");
String guest = xml.getString("booking.guest"); // "Lokesh"
int nights = xml.getInt("booking.nights"); // 3
String id = xml.getString("booking.@id"); // "42"
String status = xml.get("booking.status"); // "CONFIRMED"
For checks that are easier to write in XPath, Hamcrest’s hasXPath() matcher evaluates an XPath expression on the response document. We can mix both styles in one test.
@Test
void validatesBookingWithGPathAndXPath() {
given()
.contentType("application/xml; charset=UTF-8")
.body("<booking><guest>Lokesh</guest><nights>3</nights></booking>")
.when()
.post("/bookings")
.then()
.statusCode(201)
.contentType(ContentType.XML)
.body("booking.nights.toInteger()", greaterThan(1))
.body(hasXPath("/booking/guest", equalTo("Lokesh")))
.body(hasXPath("count(/booking/*)", equalTo("3")));
}
GPath returns element text as a String, so a numeric comparison needs toInteger() in the path. The XPath tutorial covers the XPath syntax, and for comparing whole documents while ignoring formatting, XMLUnit is the better tool.
7. Namespaces and SOAP Requests
A SOAP request is an HTTP POST with an XML envelope. SOAP 1.1 services expect the content type text/xml and a SOAPAction header that names the operation, while SOAP 1.2 uses application/soap+xml with an action parameter. Responses use namespace prefixes such as soap: and b:.
@Test
void callsSoapOperation() {
RestAssured.basePath = "";
given()
.contentType("text/xml; charset=UTF-8")
.header("SOAPAction", "getBooking")
.body("""
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body><b:getBooking xmlns:b="urn:bookings"><b:id>42</b:id></b:getBooking></soap:Body>
</soap:Envelope>
""")
.when()
.post("/ws/bookings")
.then()
.statusCode(200)
.body("Envelope.Body.booking.guest", equalTo("Lokesh"))
.body("Envelope.Body.booking.@id", equalTo("42"));
}
By default, GPath ignores the prefixes, so Envelope.Body.booking.guest finds soap:Envelope/soap:Body/b:booking/b:guest. When two elements with the same local name come from different namespaces, we declare the namespaces with XmlConfig.xmlConfig().declareNamespace(“b”, “urn:bookings”) and write paths such as b:booking.b:guest. For hasXPath() on prefixed documents, xmlConfig().namespaceAware(false) lets plain paths such as /Envelope/Body match.
8. XML Requests Compared with JSON Requests
Most of a REST-assured test is the same for both formats. The differences sit in a few settings, which explains why a JSON test copied for an XML API often fails on the first run.
| Aspect | JSON request | XML request |
|---|---|---|
| Content type | ContentType.JSON, sent as UTF-8 | application/xml; charset=UTF-8, because the default charset is ISO-8859-1 |
| Object serialization | Jackson 3, Jackson 2, Gson, Johnzon or Yasson | Jakarta XML Binding, or a String from Jackson XmlMapper |
| Records as body | Supported | Only through Jackson XmlMapper |
| Response paths | tasks[0].title | booking.guest, booking.@id |
| Extra matchers | JSON Schema | hasXPath(), XSD validation with matchesXsd() |
JSON requests with POJOs, maps and files are covered in REST-assured POST and PUT examples.
9. REST-assured XML Request FAQs
With XML APIs, the open points are the content type, XSD validation, paths that find nothing and XML in GET requests.
9.1. Which content type should an XML request use?
Use the one the API documents, which is application/xml for most REST APIs, text/xml for SOAP 1.1 and application/soap+xml for SOAP 1.2. Always add charset=UTF-8, because REST-assured otherwise adds charset=ISO-8859-1 to XML content types.
9.2. Can REST-assured validate an XML response against an XSD?
Yes. The matcher matchesXsd(xsd) from RestAssuredMatchers takes the schema as a String, File or InputStream, and matchesXsdInClasspath(“booking.xsd”) loads it from the classpath. The test fails when the response does not follow the schema.
9.3. Why does body(“guest”) not find the element?
Because GPath paths start at the root element. For <booking><guest>, the path is booking.guest, not guest. The rootPath(“booking”) call on then() sets a prefix for all following paths.
9.4. How do we extract a value from an XML response?
Call extract().path(“booking.@id”) after the checks, or get the whole document with extract().xmlPath() and read several values from it, as in section 6.
9.5. Can REST-assured send XML with a GET request?
Technically yes, with body() on a GET request, but many servers and proxies ignore or reject a GET body. APIs that take an XML query document use POST, which is what SOAP does for every operation.
10. Conclusion
An XML request in REST-assured is a POST or PUT with an XML body and the right content type. We send the body as a text block, a file, a Jakarta XML Binding object or a String from Jackson XmlMapper, and we always add charset=UTF-8 so non-ASCII data survives.
On the response side, GPath reads elements and @ attributes from the root down, hasXPath() handles XPath checks, and XmlPath extracts values. SOAP calls use the same tools with an envelope, a SOAPAction header and paths that skip the namespace prefixes.
11. References
Happy Learning !!
Hope you are doing well.
Thank you, the second method suits to my requirement of reading the XML File. However here using the above code, the body shows the content as the FILEPATH and not the File Content.
Any other way to read this XML file and use it in the Body.
I tried converting XML to JSON which partially worked. But as the API doesn’t accept JSON format, we have to rely on either XML or TEXT. Preferably XML.
Appreciate your assistance.
Can you please share the code you are using?