Content-Disposition with Unicode Filenames in Java: Fixing the IllegalArgumentException
The problem
Sometimes a web backend streams a file back to the browser directly. That usually means setting Content-Disposition on the HTTP response — Content-Disposition: inline to tell the browser to render it inline, or Content-Disposition: attachment; filename="File.txt" to trigger a download dialog with a pre-filled filename.
A minimal Spring MVC example — a GET endpoint that returns a file:
@GetMapping
public ResponseEntity<Resource> download() {
String disp = "attachment; filename=\"レポート.txt\"";
String mime = "text/plain";
Resource res = new UrlResource(...);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, disp)
.header(HttpHeaders.CONTENT_TYPE, mime)
.body(res);
}
Run it and Java throws:
java.lang.IllegalArgumentException: The Unicode character [レ] at code point [12,460]
cannot be encoded as it is outside the permitted range of 0 to 255
The fix
This isn’t just a Java thing. The HTTP spec only allows ISO-8859-1 characters in headers, which rules out CJK, Cyrillic, Greek, and most non-Latin scripts. The fix is to percent-encode the filename as UTF-8 and use the filename*=utf-8'' parameter instead of the plain filename= one.
So filename="レポート.txt" becomes filename*=utf-8''%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88.txt (note: no double quotes around the encoded value). A small helper makes this easy:
public static String filenameEncode(String name) {
try {
return java.net.URLEncoder.encode(name, "UTF-8").replace("+", "%20");
} catch (java.io.UnsupportedEncodingException e) {
e.printStackTrace();
return name;
}
}
Now update the GET method:
@GetMapping
public ResponseEntity<Resource> download() {
String disp = "attachment; filename*=utf-8''" + filenameEncode("レポート.txt");
String mime = "text/plain";
Resource res = new UrlResource(...);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, disp)
.header(HttpHeaders.CONTENT_TYPE, mime)
.body(res);
}
The example above is Java with Spring — the same pattern works with any language or framework, as long as you use its URL encoder. One gotcha: some encoders (including java.net.URLEncoder) turn space characters into + rather than %20. That’s valid for query strings but not for Content-Disposition, so replace + with %20 as the helper above does.