개주 훈련일지/🔥 히노카미 코구라(오류 수정)

오류내역) java.lang.IllegalStateException: 어떤 multi-part 설정도 제공되지 않았기 때문에, part들을 처리할 수 없습니다.

lshfood2 2025. 12. 14. 02:42

이미지 업로드 시 발생하는 오류 화면

 

CKEditor5 커스텀 빌드를 사용하여 이미지 업로드를 구현하였다.

이미지 업로드 UI는 에디터에서 정상적으로 출력되었으나

실제 업로드 기능 사용 시 아래와 같은 오류를 만나게 되었다.

 

▼ 브라우저 console 로그

Failed to load resource: the server responded with a status of 500 () 
POST http://localhost:8088/day042_totalpractice/uploadImage.do 500 (Internal Server Error)

 

▼ 이클립스 console

java.lang.IllegalStateException: 
어떤 multi-part 설정도 제공되지 않았기 때문에, 

part들을 처리할 수 없습니다.


[ 오류가 의미하는 것 ]

이 오류는 CKEditor 문제도 아니고,
이미지 업로드 로직 문제도 아니다.

 

Servlet이 multipart/form-data 요청을 처리할 준비가 안 된 상태에서

request.getPart()를 호출했기 때문에 발생한 오류다.

 

[ 오류 발생 원인 ]

CKEditor의 SimpleUploadAdapter는
이미지를 업로드할 때 다음과 같은 요청을 보낸다.

Content-Type: multipart/form-data

 

즉, 일반 POST 요청이 아니라
파일이 포함된 multipart 요청이다.

 

그런데 Servlet은 기본적으로

multipart를 처리하지 못한다

 

Servlet은

  • request.getParameter() → 기본 요청에서만 사용 가능
  • request.getPart() → multipart 설정이 되어 있어야만 사용 가능

즉, request.getPart("upload"); 에서

무조건 예외가 발생한다.

 

그래서 터진 예외가 바로 IllegalStateException:
어떤 multi-part 설정도 제공되지 않았기 때문에,
part들을 처리할 수 없습니다.

 

예외를 풀어서 해석하면 아래와 같다.

“이 요청이 multipart 요청인 건 알겠는데,
이 서블릿에는 multipart를 처리하겠다는 설정이 없다.”

 

▼ 핵심 원인 한 줄 요약

multipart/form-data 요청을 처리하는

서블릿에 @MultipartConfig 설정이 없었기 때문


[ 착각 포인트 ]

❌ 잘못된 생각

 

“UploadImageAction에서 파일을 처리하니까
UploadImageAction에 설정하면 되는 거 아닌가?”

 

 

⭕ 실제 동작 구조

실제 요청을 받는 서블릿은 FrontController

UploadImageAction은 이미 request를 받은 이후에 실행됨

 

따라서

 

multipart 설정은
Action 클래스가 아니라
FrontController에 반드시 있어야 한다

 

[ 실제 해결 방법 ]

FrontController에 @MultipartConfig 추가

@MultipartConfig(
    fileSizeThreshold = 1024 * 1024,   // 1MB
    maxFileSize = 1024 * 1024 * 10,     // 10MB
    maxRequestSize = 1024 * 1024 * 20   // 20MB
)

 

원래는 이렇게 웹어노테이션만 있었다.

@WebServlet("*.do")

또한 multipart 예외로 인해 UploadImageAction이

정상 종료되지 못했고,그 결과 ActionForward가

 

null인 상태에서 forward.isRedirect() 가 호출되며

NullPointerException이 추가로 발생했다.

 

그래서 FrontController에

아래 처리도 반드시 필요하다.

if (forward == null) {
    return;
}

 

▼ 최종 정리 코드(오류 수정 완료(

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("*.do")
@MultipartConfig(
    fileSizeThreshold = 1024 * 1024,   // 1MB
    maxFileSize = 1024 * 1024 * 10,     // 10MB
    maxRequestSize = 1024 * 1024 * 20   // 20MB
)
public class FrontController extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private ActionFactory factory;

	public FrontController() {
		super();
		factory = new ActionFactory();
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doAction(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doAction(request, response);
	}

	private void doAction(HttpServletRequest request, HttpServletResponse response)
	        throws ServletException, IOException {

	    String command = request.getRequestURI();
	    command = command.substring(request.getContextPath().length());
	    System.out.println("[로그] command : " + command);

	    Action action = factory.getAction(command);
	    System.out.println("[로그] action : " + action);

	    ActionForward forward = action.execute(request, response);

	    // ★★★★★ 이 줄이 핵심 ★★★★★
	    // 업로드처럼 응답을 이미 끝낸 경우
	    if (forward == null) {
	        return;
	    }

	    if (forward.isRedirect()) {
	        response.sendRedirect(forward.getPath());
	    } else {
	        RequestDispatcher dispatcher =
	                request.getRequestDispatcher(forward.getPath());
	        dispatcher.forward(request, response);
	    }
	}

}