기존 서버 환경
Spring 서버의 Filter 단계에서 HttpServletRequest를 캐싱하여, 이후 다른 레이어에서 로깅이나 기타 작업을 처리할 수 있도록 구성해 둔 상태였습니다.
이는 HttpServletRequest의 내용을 한 번 읽으면 다시 읽을 수 없기 때문에, 반복 조회가 가능하도록 캐싱을 해두었습니다.
문제 상황 : IOException
yml 파일에 설정한 파일 크기를 초과하는 경우, 의도한 에러가 아닌, 500 에러가 발생했습니다.
spring:
servlet:
multipart:
max-file-size: 2MB
max-request-size: 20MB
Error Log

Response Error Message
{
"timestamp": "2025-02-14T14:10:57.250+09:00",
"status": 500,
"error": "Internal Server Error",
"path": "/api/v4/admin/uploads"
}
문제 정리
Filter → Dispatcher → Controller 순으로 요청이 흘러 가는데, Dispatcher 단계에서 파일을 읽을 수 없어서 에러가 발생했습니다.
- 하지만, MaxUploadSizeExceededException 가 발생해야 하는데 @ControllerAdvice로 전달되지 않아 예외를 올바르게 처리할 수 없는 상황이었습니다.
문제 파악 : Multipart, MultipartResolver
public CachedBodyHttpServletRequest(HttpServletRequest request)
throws IOException {
super(request);
InputStream is = request.getInputStream();
cachedBody = StreamUtils.copyToByteArray(is);
}
- Multipart 요청에서 MultipartResolver가 실행되어 StandardServletMultipartResolver 를 통해 파일 정보를 읽어 MultipartHttpServletRequest를 생성합니다.
- MultipartResolver가 실행되면 Spring Servlet에 설정된 최대 파일 크기 내로 파일을 읽습니다.
- 이때, 설정한 파일 크기를 초과하는 경우 읽는 도중 InputStream이 닫히게 되어, Filter단에서 getInputStream()을 호출하는 부분에서 IOException(Stream Closed) 에러가 발생하게 됩니다.
문제 해결 : try - catch
try {
InputStream is = request.getInputStream();
cachedBody = StreamUtils.copyToByteArray(is);
} catch (IOException e) {
log.error("Error: CachedBodyHttpServletRequest InputStream is Closed");
}
- try - catch { … } 를 통해 발생하는 IOException을 잡아주어, 해당 요청이 filterChain.doFilter() 다음 Filter 및 Spring MVC로 전달되도록 하였습니다.
- 예외를 잡아주면, DispatcherServlet이 Controller로 MaxUploadSizeExceededException 전달하여, ControllerAdvice를 통해 예외를 처리할 수 있습니다.
Learning Point
1. Filter의 동작 흐름과 예외 전파 방식
FilterChain 내에서 처리되지 않은 요청은 Servlet으로 전달되지 않는다.
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CachedBodyHttpServletRequest cachingRequest = new CachedBodyHttpServletRequest(request);
...
filterChain.doFilter(cachingRequest, cachingResponse);
Filter는 Servlet Container 수준에서 동작하는 컴포넌트로, 클라이언트 요청이 Controller에 도달하기 전에 실행됩니다.
이때 filterChain.doFilter(request, response) 메서드는 다음 필터나 실제 서블릿(DispatcherServlet -> Controller)으로 요청을 넘기는 역할을 수행합니다.
filterChain.doFilter()가 호출되기 전에 예외가 발생하고, 해당 예외가 처리되지 않으면 해당 요청은 Controller까지 도달하지 못하여 필터 단계에서 요청 처리가 중단됩니다.
2. Spring ServletRequest 흐름
1. 클라이언트 요청 → 필터 체인(FilterChain) 통과 → DispatcherServlet으로 전달
2. DispatcherServlet이 Multipart 요청을 처리하며 파일 크기 검증
- 만약 파일 크기가 제한을 초과하면, Spring 내부에서 MaxUploadSizeExceededException 등의 예외 발생
3. 예외가 컨트롤러 단(@ExceptionHandler)으로 가야 하지만, 현재 Multipart 데이터를 Filter에서 로깅을 위해 읽는 과정에서 Filter 단에 IOException 예외가 발생
4. 발생한 예외를 잡아주지 않는 경우, 예외가 필터 체인으로 전파되어 Controller로 전달되지 않는다.
- 이때 필터에서 try-catch로 예외를 잡지 않았다면, 최종적으로 서블릿 컨테이너(Tomcat 등)가 처리하여 HTTP 500 응답을 반환하게 된다.
즉, DispatcherServlet이 예외를 컨트롤러(@ExceptionHandler)로 전달하지 못하고, 필터 단에서 처리된다.
3. spring.servlet.multipart.max-file-size 적용 시점
Spring Boot에서 spring.servlet.multipart.max-file-size를 설정하면,
Spring의 MultipartResolver(예: StandardServletMultipartResolver 또는 CommonsMultipartResolver)가 요청을 파싱 할 때 적용된다. 즉, DispatcherServlet에서 MultipartResolver가 동작하는 시점부터 적용된다. (일반적으로, Filter 단에서 읽지 않는다.)
'Spring Framework > Spring' 카테고리의 다른 글
Spring JDBC vs SQL Mapper vs ORM 차이점 비교 (장단점) (0) | 2025.03.28 |
---|---|
Java ORM 사용 이유 및 장단점 완벽 정리 (JPA, Hibernate) (0) | 2025.03.28 |
Spring Controller가 수백만 개의 요청을 처리하는 원리 - Spring Bean의 역할과 목적 (2) | 2025.03.18 |
Spring DI 방식 비교: 생성자 주입과 필드 주입의 차이점 정리 (0) | 2025.03.14 |
Spring ServletContainer와 SpringContainer의 차이 이해하기 (0) | 2025.03.04 |
기존 서버 환경
Spring 서버의 Filter 단계에서 HttpServletRequest를 캐싱하여, 이후 다른 레이어에서 로깅이나 기타 작업을 처리할 수 있도록 구성해 둔 상태였습니다.
이는 HttpServletRequest의 내용을 한 번 읽으면 다시 읽을 수 없기 때문에, 반복 조회가 가능하도록 캐싱을 해두었습니다.
문제 상황 : IOException
yml 파일에 설정한 파일 크기를 초과하는 경우, 의도한 에러가 아닌, 500 에러가 발생했습니다.
spring:
servlet:
multipart:
max-file-size: 2MB
max-request-size: 20MB
Error Log

Response Error Message
{
"timestamp": "2025-02-14T14:10:57.250+09:00",
"status": 500,
"error": "Internal Server Error",
"path": "/api/v4/admin/uploads"
}
문제 정리
Filter → Dispatcher → Controller 순으로 요청이 흘러 가는데, Dispatcher 단계에서 파일을 읽을 수 없어서 에러가 발생했습니다.
- 하지만, MaxUploadSizeExceededException 가 발생해야 하는데 @ControllerAdvice로 전달되지 않아 예외를 올바르게 처리할 수 없는 상황이었습니다.
문제 파악 : Multipart, MultipartResolver
public CachedBodyHttpServletRequest(HttpServletRequest request)
throws IOException {
super(request);
InputStream is = request.getInputStream();
cachedBody = StreamUtils.copyToByteArray(is);
}
- Multipart 요청에서 MultipartResolver가 실행되어 StandardServletMultipartResolver 를 통해 파일 정보를 읽어 MultipartHttpServletRequest를 생성합니다.
- MultipartResolver가 실행되면 Spring Servlet에 설정된 최대 파일 크기 내로 파일을 읽습니다.
- 이때, 설정한 파일 크기를 초과하는 경우 읽는 도중 InputStream이 닫히게 되어, Filter단에서 getInputStream()을 호출하는 부분에서 IOException(Stream Closed) 에러가 발생하게 됩니다.
문제 해결 : try - catch
try {
InputStream is = request.getInputStream();
cachedBody = StreamUtils.copyToByteArray(is);
} catch (IOException e) {
log.error("Error: CachedBodyHttpServletRequest InputStream is Closed");
}
- try - catch { … } 를 통해 발생하는 IOException을 잡아주어, 해당 요청이 filterChain.doFilter() 다음 Filter 및 Spring MVC로 전달되도록 하였습니다.
- 예외를 잡아주면, DispatcherServlet이 Controller로 MaxUploadSizeExceededException 전달하여, ControllerAdvice를 통해 예외를 처리할 수 있습니다.
Learning Point
1. Filter의 동작 흐름과 예외 전파 방식
FilterChain 내에서 처리되지 않은 요청은 Servlet으로 전달되지 않는다.
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CachedBodyHttpServletRequest cachingRequest = new CachedBodyHttpServletRequest(request);
...
filterChain.doFilter(cachingRequest, cachingResponse);
Filter는 Servlet Container 수준에서 동작하는 컴포넌트로, 클라이언트 요청이 Controller에 도달하기 전에 실행됩니다.
이때 filterChain.doFilter(request, response) 메서드는 다음 필터나 실제 서블릿(DispatcherServlet -> Controller)으로 요청을 넘기는 역할을 수행합니다.
filterChain.doFilter()가 호출되기 전에 예외가 발생하고, 해당 예외가 처리되지 않으면 해당 요청은 Controller까지 도달하지 못하여 필터 단계에서 요청 처리가 중단됩니다.
2. Spring ServletRequest 흐름
1. 클라이언트 요청 → 필터 체인(FilterChain) 통과 → DispatcherServlet으로 전달
2. DispatcherServlet이 Multipart 요청을 처리하며 파일 크기 검증
- 만약 파일 크기가 제한을 초과하면, Spring 내부에서 MaxUploadSizeExceededException 등의 예외 발생
3. 예외가 컨트롤러 단(@ExceptionHandler)으로 가야 하지만, 현재 Multipart 데이터를 Filter에서 로깅을 위해 읽는 과정에서 Filter 단에 IOException 예외가 발생
4. 발생한 예외를 잡아주지 않는 경우, 예외가 필터 체인으로 전파되어 Controller로 전달되지 않는다.
- 이때 필터에서 try-catch로 예외를 잡지 않았다면, 최종적으로 서블릿 컨테이너(Tomcat 등)가 처리하여 HTTP 500 응답을 반환하게 된다.
즉, DispatcherServlet이 예외를 컨트롤러(@ExceptionHandler)로 전달하지 못하고, 필터 단에서 처리된다.
3. spring.servlet.multipart.max-file-size 적용 시점
Spring Boot에서 spring.servlet.multipart.max-file-size를 설정하면,
Spring의 MultipartResolver(예: StandardServletMultipartResolver 또는 CommonsMultipartResolver)가 요청을 파싱 할 때 적용된다. 즉, DispatcherServlet에서 MultipartResolver가 동작하는 시점부터 적용된다. (일반적으로, Filter 단에서 읽지 않는다.)
'Spring Framework > Spring' 카테고리의 다른 글
Spring JDBC vs SQL Mapper vs ORM 차이점 비교 (장단점) (0) | 2025.03.28 |
---|---|
Java ORM 사용 이유 및 장단점 완벽 정리 (JPA, Hibernate) (0) | 2025.03.28 |
Spring Controller가 수백만 개의 요청을 처리하는 원리 - Spring Bean의 역할과 목적 (2) | 2025.03.18 |
Spring DI 방식 비교: 생성자 주입과 필드 주입의 차이점 정리 (0) | 2025.03.14 |
Spring ServletContainer와 SpringContainer의 차이 이해하기 (0) | 2025.03.04 |