## What `multiPart` actually does
`RequestSpecification.multiPart(...)` sends nothing by itself. Each call appends one entry to a list the request specification carries, and that list is turned into a payload only when the verb — `post`, `put` or `patch` — fires. Two things follow immediately: a request may carry as many parts as you need, and the call order is the part order. Nothing in the DSL limits you to a single attachment.
The moment that list is non-empty, REST Assured takes over the request's `Content-Type`. If you set none, it composes `multipart/` plus `MultiPartConfig.defaultSubtype()`, which ships as `form-data`. So uploading a weigh-in photo to a falconry weight-log API needs no content-type call at all:
```java
given().multiPart(new File("scale-2026-09-07.jpg"))
.when().post("/hawks/HW-118/weigh-ins/WI-9032/attachments")
.then().statusCode(201);
```
That request goes out as `multipart/form-data` with one part.
## The control name, and where its default comes from
The **control name** is the field name the receiving endpoint binds the part to — the `name` attribute of the `<input type="file">` in the form the endpoint was written for. Every overload except the one-argument `multiPart(File)` takes it as its first argument. `multiPart(File)` has nowhere to read it from, so it falls back to `MultiPartConfig.defaultControlName()`, whose value is the literal string `file`.
That default is configuration, not a constant welded into the call:
- `given().config(config().multiPartConfig(multiPartConfig().defaultControlName("scalePhoto")))` changes it for one request.
- Assigning the same config to `RestAssured.config` changes it for the whole suite.
- `multiPart("scalePhoto", file)` overrides it for one part and is almost always the clearer choice.
## Which overload fills in which default
`RequestSpecification` declares **13** `multiPart` overloads. They differ in what content they carry and in which of the three per-part attributes — control name, file name, mime type — you supply versus REST Assured supplies.
| overload family | how many | file name comes from | part type default |
| --- | --- | --- | --- |
| `multiPart(File)` | 1 | `File.getName()` | `application/octet-stream` |
| `multiPart(name, File[, mimeType])` | 2 | `File.getName()` | `application/octet-stream` |
| `multiPart(name, fileName, byte[][, mimeType])` | 2 | your argument | `application/octet-stream` |
| `multiPart(name, fileName, InputStream[, mimeType])` | 2 | your argument | `application/octet-stream` |
| `multiPart(name, contentBody[, mimeType])` | 2 | not sent for text content | `text/plain` |
| `multiPart(name, Object[, mimeType])` | 2 | not sent for text content | mime type drives the mapper |
| `multiPart(name, fileName, Object, mimeType)` | 1 | your argument | your argument |
| `multiPart(MultiPartSpecification)` | 1 | the builder, else the config default | per content kind |
Two readings of that table matter in practice:
- The `byte[]` and `InputStream` families have **no** overload without an explicit file name, because there is nothing to derive one from.
- Overload selection happens at compile time on the static type of the argument, so `multiPart("falconerId", "F-4417")` binds to the raw-`String` overload while `multiPart("weighIn", weighInPojo)` binds to the object overload and serializes.
## The builder, for what the overloads do not cover
When you need a combination the 13 overloads do not offer — per-part headers, an explicit charset, or no file name at all — build the part instead of calling an overload:
1. `new MultiPartSpecBuilder(content)` accepts an `Object`, `String`, `byte[]`, `InputStream` or `File`.
2. `.controlName(...)` and `.fileName(...)` set those attributes **and mark them explicitly specified**, which is what stops `MultiPartConfig` substituting its own defaults at send time.
3. `.mimeType(...)`, `.charset(...)`, `.header(name, value)` and `.headers(map)` fill in the rest.
4. `.emptyFileName()` is literally `fileName(null)` — an explicit "no file name".
5. `.build()` yields a `MultiPartSpecification` for `multiPart(spec)`.
`charset(...)` refuses `byte[]` and `InputStream` content with an `IllegalArgumentException`, since there is no text there to encode.
## Pitfalls worth rehearsing
- **Do not add `contentType(ContentType.JSON)` "to be safe".** Once a request has parts, any content type that neither starts with `multipart/` nor contains `multipart+` raises `IllegalArgumentException` before the request leaves the process.
- **`body(...)` and `multiPart(...)` do not compose.** With parts present the assembled body content becomes an empty byte array and the multipart encoder rebuilds the payload from the part list, so the `body(...)` argument is silently discarded.
- **The control name is the server's contract.** Getting it wrong normally surfaces as a 400 naming a missing field, not as an upload error, so read the endpoint's field name rather than guessing.
- **A multipart request still needs a body-bearing verb.** `post`, `put` and `patch` carry parts; a `get` with parts is not what any upload endpoint expects.