java
64 lines · 8 steps
Parsing multipart form data by hand in Java
A byte-level parser that splits a multipart/form-data body on its boundary and extracts each uploaded file.
Explained by
highlit
1public final class MultipartParser {
2
3 public record FilePart(String name, String filename, String contentType, byte[] content) {}
4
5 private final byte[] data;
6 private final byte[] boundary;
7
8 public MultipartParser(byte[] data, String contentType) {
9 this.data = data;
10 String marker = "boundary=";
11 int i = contentType.indexOf(marker);
12 if (i < 0) throw new IllegalArgumentException("missing boundary");
13 String b = contentType.substring(i + marker.length()).trim();
14 if (b.startsWith("\"")) b = b.substring(1, b.length() - 1);
15 this.boundary = ("--" + b).getBytes(StandardCharsets.ISO_8859_1);
16 }
17
18 public List<FilePart> parse() {
19 List<FilePart> parts = new ArrayList<>();
20 int pos = indexOf(boundary, 0);
21 while (pos >= 0) {
22 int start = pos + boundary.length;
23 if (start + 2 <= data.length && data[start] == '-' && data[start + 1] == '-') break;
24 start += 2;
25 int headerEnd = indexOf("\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1), start);
26 if (headerEnd < 0) break;
27 String headers = new String(data, start, headerEnd - start, StandardCharsets.ISO_8859_1);
28 int bodyStart = headerEnd + 4;
29 int next = indexOf(boundary, bodyStart);
30 int bodyEnd = next - 2;
31 FilePart part = buildPart(headers, Arrays.copyOfRange(data, bodyStart, bodyEnd));
32 if (part != null) parts.add(part);
33 pos = next;
34 }
35 return parts;
36 }
37
38 private FilePart buildPart(String headers, byte[] body) {
39 String name = extract(headers, "name=\"", "\"");
40 String filename = extract(headers, "filename=\"", "\"");
41 if (filename == null) return null;
42 String type = extract(headers, "Content-Type: ", "\r\n");
43 return new FilePart(name, filename, type != null ? type.trim() : "application/octet-stream", body);
44 }
45
46 private String extract(String source, String prefix, String suffix) {
47 int i = source.indexOf(prefix);
48 if (i < 0) return null;
49 i += prefix.length();
50 int j = source.indexOf(suffix, i);
51 return j < 0 ? source.substring(i) : source.substring(i, j);
52 }
53
54 private int indexOf(byte[] needle, int from) {
55 outer:
56 for (int i = from; i <= data.length - needle.length; i++) {
57 for (int j = 0; j < needle.length; j++) {
58 if (data[i + j] != needle[j]) continue outer;
59 }
60 return i;
61 }
62 return -1;
63 }
64}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Multipart bodies must be scanned as raw bytes, not decoded strings, so file content survives intact.
- 2The boundary from the Content-Type header is the delimiter that separates every part in the body.
- 3Splitting a format into locate-header, extract-metadata, and copy-body phases keeps a hand-rolled parser manageable.
Related explainers
java
public interface GitHubClient { @GetExchange("/users/{username}") GitHubUser getUser(@PathVariable String username);
Declarative HTTP clients in Spring
http-client
declarative-api
proxy
Intermediate
8 steps
java
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;
Building a trie for autocomplete in Java
trie
prefix-tree
recursion
Intermediate
8 steps
javascript
import { useReducer, useEffect } from "react"; const initialState = { status: "idle", data: null, error: null };
Building a data-fetching hook in React
custom-hooks
usereducer
data-fetching
Intermediate
9 steps
rust
use std::net::Ipv4Addr; use std::str::FromStr; #[derive(Debug, Clone, Copy)]
Parsing and matching IPv4 CIDR ranges in Rust
bitwise-operations
parsing
error-handling
Intermediate
8 steps
java
@RestController @RequestMapping("/api/products") public class ProductSearchController {
Binding collection query params in Spring
rest-api
query-parameters
dependency-injection
Intermediate
6 steps
java
import java.util.ArrayDeque; import java.util.Deque; import java.util.Map;
Evaluating math expressions with two stacks
stacks
parsing
operator-precedence
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/parsing-multipart-form-data-by-hand-in-java-explained-java-213c/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.