Spring Boot 文件上传大小限制配置全解析
一、问题本质
当客户端上传文件超过服务端限制时,Servlet 容器(Tomcat)直接拒绝请求,返回 HTTP 413 Request Entity Too Large,请求根本不会到达你的 Controller 代码。
客户端上传 5MB 文件
↓
Tomcat 检查:max-file-size = 1MB(默认)
↓
5MB > 1MB → 直接拒绝,返回 413
↓
你的 Controller 代码完全不会执行
二、Spring Boot 文件上传相关配置项
yaml
spring:
servlet:
multipart:
enabled: true # 是否启用 multipart 支持
max-file-size: 10MB # 单个文件最大大小
max-request-size: 30MB # 整个请求体最大大小
file-size-threshold: 0B # 超过多大写入临时文件(0表示始终写磁盘)
location: # 临时文件存储目录
resolve-lazily: false # 是否延迟解析 multipart 请求
2.1 各配置项详解
| 配置项 | 默认值 | 含义 | 触发时机 |
|---|---|---|---|
enabled |
true | 是否开启文件上传功能 | 应用启动时 |
max-file-size |
1MB | 单个上传文件的最大大小 | Tomcat 解析请求体时 |
max-request-size |
10MB | 整个 multipart 请求的最大大小(所有文件+表单字段之和) | Tomcat 解析请求体时 |
file-size-threshold |
0B | 文件大小超过此值时写入磁盘临时文件,否则保留在内存 | 解析文件时 |
location |
系统临时目录 | 临时文件存放路径 | 写入临时文件时 |
resolve-lazily |
false | true 则延迟到实际使用 file 时才解析 | 请求进入时 |
2.2 max-file-size vs max-request-size
一个 multipart 请求可能包含:
├── file1: 8MB(图片)
├── file2: 5MB(文档)
├── name: "张三"(文本字段,几字节)
└── 总请求体大小: ~13MB + boundary 开销
max-file-size = 10MB → file1(8MB) 通过,如果 file1 是 12MB 则被拒
max-request-size = 30MB → 整体 13MB 通过,如果总体超过 30MB 则被拒
单文件接口 :max-request-size 略大于 max-file-size 即可(额外空间给表单字段和 boundary)。
多文件接口 :max-request-size 应为 max-file-size × 文件数 + 余量。
2.3 file-size-threshold 的作用
上传文件 → Tomcat 解析
├── 文件 < file-size-threshold → 保留在 JVM 内存中(快,但占堆内存)
└── 文件 >= file-size-threshold → 写入磁盘临时文件(慢,但不占堆内存)
| 场景 | 建议值 |
|---|---|
| 文件很小(头像、图标) | 256KB~1MB(保留内存,减少磁盘IO) |
| 文件较大(Excel、视频) | 0B(默认,始终写磁盘,保护堆内存) |
| 高并发小文件 | 适当调大,减少磁盘写入 |
2.4 resolve-lazily 的作用
yaml
resolve-lazily: true
- false(默认):请求一进来就解析整个 multipart body
- true :延迟到 Controller 中第一次使用
MultipartFile参数时才解析
用途:配合异常处理,在解析失败时能被全局异常处理器捕获(默认模式下解析在 Filter 层就完成了,异常可能无法被 @ControllerAdvice 捕获)。
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
三、大小单位写法
Spring Boot 支持以下写法:
| 写法 | 含义 |
|---|---|
10MB |
10 兆字节 |
10485760 |
精确字节数(10×1024×1024) |
1GB |
1 吉字节 |
512KB |
512 千字节 |
-1 |
不限制大小 |
四、超出限制时的异常处理
4.1 默认行为
超出 max-file-size 或 max-request-size 时,Spring 抛出 MaxUploadSizeExceededException(继承自 MultipartException)。
默认情况下返回 413 或 500 错误页,对用户不友好。
4.2 全局异常处理器捕获
java
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 文件大小超出限制.
*/
@ExceptionHandler(MaxUploadSizeExceededException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> handleMaxUploadSize(MaxUploadSizeExceededException e) {
return Map.of(
"success", false,
"errorMsg", "上传文件过大,请控制在规定大小以内"
);
}
/**
* Multipart 解析异常(文件损坏、格式错误等).
*/
@ExceptionHandler(MultipartException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> handleMultipart(MultipartException e) {
return Map.of(
"success", false,
"errorMsg", "文件上传失败:" + e.getMessage()
);
}
}
4.3 配合 resolve-lazily 使用
如果全局异常处理器捕获不到 MaxUploadSizeExceededException,可以开启延迟解析:
yaml
spring:
servlet:
multipart:
resolve-lazily: true
这样异常会在 Controller 方法执行时抛出,能被 @ControllerAdvice 正常捕获。
五、两层限制策略
生产环境推荐「Servlet 容器兜底 + 代码精确校验」双层策略:
┌─────────────────────────────────────┐
请求进入 ──→ │ Servlet 容器层(yml 配置) │
│ max-file-size: 30MB │
│ 作用:防御恶意超大请求,保护服务器 │
│ 超出时:直接 413 拒绝 │
└─────────────────────┬───────────────┘
↓ 通过
┌─────────────────────────────────────┐
│ Controller 代码层 │
│ if (file.getSize() > 20MB) │
│ 作用:业务级精确校验 │
│ 超出时:返回友好提示 │
└─────────────────────┬───────────────┘
↓ 通过
┌─────────────────────────────────────┐
│ Service 业务逻辑 │
└─────────────────────────────────────┘
为什么需要两层:
| 只有 yml | 只有代码 | 两层配合 |
|---|---|---|
| 用户看到 413 错误页,不友好 | 恶意超大文件能打进来,Tomcat 解析消耗内存 | 兼顾安全和体验 |
设值策略:
yml(兜底)> 代码(业务限制)> 实际最大文件
示例:yml 设 30MB > 代码限制 20MB > 实际文件最大 ~8MB
六、完整示例
6.1 application.yml
yaml
spring:
servlet:
multipart:
enabled: true
max-file-size: 30MB # Servlet 容器兜底:允许最大30MB进来
max-request-size: 35MB # 请求体总大小:文件+表单字段
file-size-threshold: 0B # 始终写临时文件,保护堆内存
resolve-lazily: true # 延迟解析,便于异常处理器捕获
server:
port: 8080
tomcat:
max-swallow-size: 30MB # Tomcat 读取请求体的最大大小(需与上面一致)
6.2 Controller
java
package com.example.controller;
import java.util.Map;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/api/file")
public class FileUploadController {
/** 业务层文件大小限制:20MB. */
private static final long MAX_FILE_SIZE = 20L * 1024 * 1024;
/** 允许的文件类型. */
private static final String[] ALLOWED_EXTENSIONS = {".xlsx", ".xls", ".csv"};
@PostMapping("/upload")
public Map<String, Object> upload(
@RequestParam("file") MultipartFile file,
@RequestParam("operator") String operator) {
// 1. 空文件校验
if (file == null || file.isEmpty()) {
return Map.of("success", false, "errorMsg", "请选择要上传的文件");
}
// 2. 文件大小校验(业务级,给用户友好提示)
if (file.getSize() > MAX_FILE_SIZE) {
return Map.of("success", false, "errorMsg",
"文件大小不能超过20MB,当前文件大小:" + formatSize(file.getSize()));
}
// 3. 文件类型校验
String fileName = file.getOriginalFilename();
if (!isAllowedExtension(fileName)) {
return Map.of("success", false, "errorMsg",
"不支持的文件格式,仅支持:xlsx、xls、csv");
}
// 4. 业务处理...
return Map.of("success", true, "data", Map.of(
"fileName", fileName,
"fileSize", formatSize(file.getSize()),
"operator", operator
));
}
private boolean isAllowedExtension(String fileName) {
if (fileName == null) return false;
String lower = fileName.toLowerCase();
for (String ext : ALLOWED_EXTENSIONS) {
if (lower.endsWith(ext)) return true;
}
return false;
}
private String formatSize(long bytes) {
if (bytes < 1024) return bytes + "B";
if (bytes < 1024 * 1024) return String.format("%.1fKB", bytes / 1024.0);
return String.format("%.1fMB", bytes / (1024.0 * 1024));
}
}
6.3 全局异常处理器
java
package com.example.config;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
/**
* 全局异常处理器.
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理文件超出 Servlet 容器层限制的情况.
* 当文件超过 yml 中配置的 max-file-size 时触发.
*/
@ExceptionHandler(MaxUploadSizeExceededException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> handleMaxUploadSize(MaxUploadSizeExceededException e) {
return Map.of(
"success", false,
"errorMsg", "文件过大,服务器最大允许30MB"
);
}
/**
* 处理其他 Multipart 解析异常.
*/
@ExceptionHandler(MultipartException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> handleMultipartException(MultipartException e) {
return Map.of(
"success", false,
"errorMsg", "文件上传失败"
);
}
}
6.4 测试效果
bash
# 正常文件(5MB)→ 通过两层检查,正常处理
curl -F "file=@small.xlsx" -F "operator=test" http://localhost:8080/api/file/upload
# {"success":true,"data":{"fileName":"small.xlsx","fileSize":"5.2MB","operator":"test"}}
# 中等文件(25MB)→ 通过 yml 30MB 限制,但被代码 20MB 校验拦截
curl -F "file=@medium.xlsx" -F "operator=test" http://localhost:8080/api/file/upload
# {"success":false,"errorMsg":"文件大小不能超过20MB,当前文件大小:25.3MB"}
# 超大文件(50MB)→ 被 yml 30MB 限制拦截,触发全局异常处理器
curl -F "file=@large.xlsx" -F "operator=test" http://localhost:8080/api/file/upload
# {"success":false,"errorMsg":"文件过大,服务器最大允许30MB"}
七、常见问题
7.1 Tomcat 的 max-swallow-size
yaml
server:
tomcat:
max-swallow-size: 30MB
Tomcat 有一个独立的配置 max-swallow-size(默认 2MB),作用是:当请求被拒绝(如413)时,Tomcat 是否继续读取剩余请求体。如果不配置,客户端可能收到 connection reset 而非正常的错误响应。
建议设为和 max-request-size 一致。
7.2 Nginx 反向代理的限制
如果服务前面有 Nginx,还需要配置 Nginx 的 client_max_body_size:
nginx
server {
client_max_body_size 30m; # 必须 >= Spring Boot 的 max-request-size
}
否则 Nginx 层面就返回 413 了,请求到不了 Spring Boot。
7.3 完整链路的大小限制
客户端 → Nginx(client_max_body_size) → Tomcat(max-swallow-size)
→ Spring(max-request-size / max-file-size) → Controller(代码校验)
每一层都要 >= 你期望允许的最大文件大小
八、不同场景的推荐配置
| 场景 | max-file-size | max-request-size | 代码校验 |
|---|---|---|---|
| 头像上传(< 2MB) | 5MB | 5MB | 2MB |
| Excel 导入(< 20MB) | 30MB | 35MB | 20MB |
| 视频上传(< 500MB) | 600MB | 600MB | 500MB |
| 不限制 | -1 | -1 | 按需 |
原则:yml 配置比代码校验宽松 30%~50%,作为安全兜底。