In REST Assured, which Content-Type and boundary does a multiPart() request actually send?
answer
- subtype comes from configuration
- an explicit type must stay multipart
- boundary has a three-step fallback
- pin it when the payload must be stable
basics
~20 sREST Assured builds the request type as multipart/ plus MultiPartConfig.defaultSubtype(), which ships as form-data. The boundary is resolved in three steps. One already present in your own content type wins, otherwise MultiPartConfig.defaultBoundary(), otherwise a freshly generated random token.
solid answer
~50 sWith parts present and no `contentType()` call, the request type is `multipart/` plus `MultiPartConfig.defaultSubtype()` — `form-data` out of the box, and `defaultSubtype("mixed")` gives `multipart/mixed`. An explicit `contentType(...)` wins over that default, but it **must** start with `multipart/` or contain `multipart+`; anything else raises `IllegalArgumentException` before the request leaves. The boundary is resolved in three steps: a boundary already present in your content type is kept, otherwise `MultiPartConfig.defaultBoundary()` is used, otherwise REST Assured generates a random 30-to-40 character token. When it supplies one, it removes and rewrites the `Content-Type` header so exactly one is sent, with `; boundary="..."` appended. Note that no charset is appended to a multipart type, and `MultiPartConfig.defaultCharset(...)` is honoured only when `HttpClientConfig.httpMultipartMode()` has been set to something other than its default, so configuring that charset on its own changes nothing at all.
code
java · 16 linesimport java.io.File;
import static io.restassured.RestAssured.given;
import static io.restassured.config.MultiPartConfig.multiPartConfig;
import static io.restassured.config.RestAssuredConfig.config;
// Sends Content-Type: multipart/mixed; boundary="mews-boundary-01"
given()
.config(config().multiPartConfig(multiPartConfig()
.defaultSubtype("mixed")
.defaultBoundary("mews-boundary-01")))
.multiPart("scalePhoto", new File("scale-2026-09-07.jpg"), "image/jpeg")
.when()
.post("/hawks/HW-118/weigh-ins/WI-9032/attachments")
.then()
.statusCode(201);go deeper
Know that you normally set nothing: multipart requests get their content type and boundary automatically. Recognising the boundary in a request log is enough at this level.
Explain the three-step boundary fallback and where the subtype comes from, and know that an explicit content type must still be a multipart one.
Bring the operational angle: a random boundary makes captured payloads unstable, an explicit content type on an upload throws, and the multipart charset needs the mode changed too.
Judge when pinning a boundary is worth the coupling it creates between a suite and a stub, versus matching on parsed parts so the assertion survives an implementation change.
## Where the request's content type comes from A REST Assured request with at least one `multiPart(...)` call resolves its own `Content-Type` before anything is sent. If you set none, it composes the type from configuration: the literal prefix `multipart/` followed by `MultiPartConfig.defaultSubtype()`, which ships as `form-data`. Change that one setting and every multipart request in scope changes with it: - `multiPartConfig().defaultSubtype("form-data")` — the shipped value, giving `multipart/form-data`. - `multiPartConfig().defaultSubtype("mixed")` — gives `multipart/mixed`, for endpoints that ask for it. - An explicit `contentType("multipart/mixed")` on the request — **wins over the configured default**, so the two are not in competition; the call site is simply more specific. There is a hard constraint on that explicit call. When the part list is non-empty, the content type must start with `multipart/` or contain `multipart+`. A `contentType(ContentType.JSON)` added "to be safe" alongside an upload therefore fails with `IllegalArgumentException`, complaining that the content type is not valid when using multiparts. The failure happens while the request is being assembled, so no call is made. ## How the boundary is chosen A multipart payload needs a delimiter that separates the parts, and REST Assured resolves it in a fixed order: 1. **A boundary already in your content type wins.** If you wrote `contentType("multipart/form-data; boundary=mews-01")`, that value is used and your header is left exactly as you wrote it. 2. **Otherwise `MultiPartConfig.defaultBoundary()` is used.** It ships as `null`, so this step is a no-op until you configure it. 3. **Otherwise one is generated.** REST Assured builds a random token of 30 to 40 characters drawn from letters, digits, hyphen and underscore. In cases 2 and 3 — that is, whenever the boundary did not come from your own header — REST Assured **removes the `Content-Type` header and writes it again** with `; boundary="..."` appended, so that exactly one content-type header is sent rather than two. That third step is the one that surprises people comparing request logs: two runs of the same test produce two different boundaries, so a byte-for-byte comparison of the outgoing request never matches. If a test genuinely needs a stable request — an approval test over a captured payload, or a stub that matches on the raw body — pin it with `multiPartConfig().defaultBoundary("mews-boundary-01")`. ## Charsets on a multipart request Two charset behaviours around multipart differ from the rest of the DSL, and both trip people up: | behaviour | on a normal body | on a multipart request | | --- | --- | --- | | default charset appended to `Content-Type` | yes, `; charset=ISO-8859-1` | no, multipart types are excluded | | `MultiPartConfig.defaultCharset(...)` | not applicable | applied **only** when the multipart mode is changed | The second row is the sharp edge. `MultiPartConfig.defaultCharset(...)` affects how the multipart envelope itself is encoded, and it is honoured only when `HttpClientConfig.httpMultipartMode()` has been set to something other than its default of `STRICT`. Setting the charset alone changes nothing; you have to set both: ```java given().config(config() .httpClient(httpClientConfig().httpMultipartMode(BROWSER_COMPATIBLE)) .multiPartConfig(multiPartConfig().defaultCharset("UTF-8"))) .multiPart("weighInLog", new File("harris-weights.csv"), "text/csv") .when().post("/hawks/HW-118/weigh-ins/imports") .then().statusCode(202); ``` Note also that this charset governs the envelope — control names and the like — not the content of an individual part. A part's own charset is set per part on `MultiPartSpecBuilder.charset(...)`, or by putting it in the part's mime type as `"text/csv; charset=UTF-8"`. ## Per-part types, which are a separate layer The request's content type describes the envelope; each part carries its own. When you do not name one, it is derived from the part's content shape: - `File`, `InputStream` and `byte[]` content default to `application/octet-stream`. - `String` content, and any object converted to text, defaults to `text/plain`. So a falconry weight-log upload of a photo plus a CSV sends one `multipart/form-data` request whose two parts are typed `application/octet-stream` and, if you named it, `text/csv`. Naming the part type matters for any endpoint that dispatches on it, and it is the argument the three-argument `multiPart(name, file, mimeType)` overload exists to carry. ## The short version to say in an interview - Envelope type: `multipart/` plus the configured default subtype, `form-data` unless changed. - An explicit content type wins, but must be a multipart one or the call throws. - Boundary: yours if you wrote one, else the configured default, else a random 30-to-40 character token, appended by rewriting the header. - No charset is appended to a multipart content type, and the multipart charset setting needs the multipart mode changed before it does anything. - Part types are separate from the envelope and default by content shape.
- Why would you ever pin MultiPartConfig.defaultBoundary() in a test suite?Because the generated boundary is random on every run, so any assertion or stub match over the raw outgoing payload is unstable. Pinning it makes the request byte-for-byte reproducible, which matters for approval-style comparisons and for stub servers matching on the body. It is a testing convenience only, and a fixed boundary must still not appear inside any part's content.
- What happens if you call contentType(ContentType.JSON) on a request that also has parts?It fails with `IllegalArgumentException` during assembly, saying the content type is not valid when using multiparts and must start with `multipart/` or contain `multipart+`. No request is sent. The instinct to declare JSON because one part carries JSON is the usual cause; set the mime type on that part instead and leave the envelope alone.
saying these in an interview costs you the question
- Thinks you must set the boundary yourself for uploads to work
- Says an explicit contentType() can be any type once parts exist
- Believes charset is appended to a multipart content type
- Expects defaultCharset() to work without changing the multipart mode
- Confuses the envelope content type with a part's own mime type