[Interceptor] java.lang.ClassCastException: class org.springframework.web.servlet.resource.ResourceHttpRequestHandler cannot be cast to class org.springframework.web.method.HandlerMethod (org.springframework.web.servlet.resource.ResourceHttpRequestHandler

2025. 11. 6. 13:16·트러블슈팅

⚠️ 에러 메세지

2025-11-06T11:29:29.683+09:00 ERROR 47426 --- [ch2] [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.ClassCastException: class org.springframework.web.servlet.resource.ResourceHttpRequestHandler cannot be cast to class org.springframework.web.method.HandlerMethod (org.springframework.web.servlet.resource.ResourceHttpRequestHandler and org.springframework.web.method.HandlerMethod are in unnamed module of loader 'app')] with root cause

java.lang.ClassCastException: class org.springframework.web.servlet.resource.ResourceHttpRequestHandler cannot be cast to class org.springframework.web.method.HandlerMethod (org.springframework.web.servlet.resource.ResourceHttpRequestHandler and org.springframework.web.method.HandlerMethod are in unnamed module of loader 'app')
	at com.fastcampus.ch2.PerformanceInterceptor.preHandle(PerformanceInterceptor.java:24) ~[classes/:na]
	at org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle(HandlerExecutionChain.java:146) ~[spring-webmvc-6.2.10.jar:6.2.10]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1084) ~[spring-webmvc-6.2.10.jar:6.2.10]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.2.10.jar:6.2.10]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.2.10.jar:6.2.10]
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.2.10.jar:6.2.10]
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) ~[tomcat-embed-core-10.1.44.jar:6.0]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.2.10.jar:6.2.10]
	at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.44.jar:6.0]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.44.jar:10.1.44]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at com.fastcampus.ch2.PerformanceFilter.doFilter(PerformanceFilter.java:20) ~[classes/:na]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.2.10.jar:6.2.10]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.10.jar:6.2.10]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.2.10.jar:6.2.10]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.10.jar:6.2.10]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.2.10.jar:6.2.10]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.10.jar:6.2.10]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) ~[tomcat-embed-core-10.1.44.jar:10.1.44]
	at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]

method.getMethod() = public org.springframework.http.ResponseEntity org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(jakarta.servlet.http.HttpServletRequest)
method.getBean() = org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController@705e081b
[/error] time = 3

 

 

📝 문제 상황

Interceptor(공통 전처리, 후처리의 단일 책임원칙 적용) 구현할 때 preHandle 메서드의 매개변수 handle이 HandlerMethod로 캐스팅 할 때 에러 발생함.

 HandlerMethod method = (HandlerMethod) handler; // ClassCastException 발생

인터셉터 코드

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Component // WebApplicationContext 객체 저장소에 빈 객체로 등록
public class PerformanceInterceptor implements HandlerInterceptor { // 단일 책임 원칙 (SRP) - 하나의 메서드는 하나의 책임만 갖는다
    // Filter 구현에서 단일 책임원칙에 따라 메서드를 전처리와 후처리로 분리

    //long startTime; // iv - 인스턴스 변수, 싱글톤(객체1개)이라서 여러 쓰레드가 하나의 인스턴스 변수 공유. 값 덮여쓰여질수있음.
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // uri 확인
//        String uri = request.getRequestURI();        // 컨텍스트패스 포함
//        String ctx = request.getContextPath();       // 보통 "" 또는 "/앱이름"
//        String servlet = request.getServletPath();   // DispatcherServlet 기준 경로
//        System.out.printf("[INTC] uri=%s, ctx=%s, servletPath=%s, handlerClass=%s%n",
//                uri, ctx, servlet, handler.getClass().getName());


        // 1. 전처리 작업
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);



        // handler - 요청하고 연결된 컨트롤러 메서드
        HandlerMethod method = (HandlerMethod) handler; // ClassCastException 발생

        System.out.println("method.getMethod() = " + method.getMethod()); // URL하고 연결된 메서드
        System.out.println("method.getBean() = " + method.getBean());   // 메서드가 포함된 컨트롤러
        // return true; // 다음 인터셉터나 컨트롤러를 호출. false면 호출 안함.
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    // 2. 후처리 작업
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 전처리에서 작업한 값을 가져오기.
        long startTime = (long)request.getAttribute("startTime");

        long endTime = System.currentTimeMillis();
        System.out.print("[" + ((HttpServletRequest)request).getRequestURI() + "]");
        System.out.println(" time = " + (endTime - startTime));

        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
}

 

✅ 원인 및 해결책

원인1: Dispatcher Servlet이 사용자로부터 받는 요청의 종류가 다양함

기존 생각

기존에 알고있던 처리 순서는 DS(Dispatcher Servlet)가 사용자 모든 요청을 받으면 Interceptor를 통해 전처리하고 컨트롤러 호출하여 처리한 다음 후처리하고 응답을 ModelAndView를 통해 반환하는 것으로 이해했다.

 

추상화된 처리 순서

 

알고보니 에러 메세지에서 ClassCastException에 엄청난 힌트가 있었다.

 

class org.springframework.web.servlet.resource.ResourceHttpRequestHandler 클래스가 실제로 Interceptor 의 handler에 넘겨진 객체의 타입이었다.

 

즉, 리소스(Resource) 에는 동적 리소스, 정적 리소스가 있다. 종류에 따라 Dispatcher Servlet이 처리하는 순서가 달라진다.

  • 동적 리소스 : 사용자 요청마다 내용이 바뀌는 것으로 실행기가 필요하고
  • 정적 리소스 : 내용이 바뀌는 않는 것으로 파일 같은 예가 있다.

ResourceHttpHandler의 경우 HTTP 요청이 정적 리소스(e.g. *.css )인 경우에 Url요청에 연결된 @Controller 애너테이션이 붙은 클래스의 메서드가 호출되지 않는 것이다.

정적 리소스 요청이 발생할 수 있으면 preHandle() 전처리 메서드 구성할 때 HandlerMethod타입과 ResourceHttpHandler를 분기해서 캐스팅을 해야한다.

해결책 1: (채택) handle이 가리키는 타입을 분기해서 캐스팅 or instanceof

사용자 요청을 구성하는 여러 종류를 알아야하고 그것을 모두 분기해야함. 혹은 ! (not)으로 HandlerMethod타입만 캐스팅하도록 instacneof 를 사용

 

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Component // WebApplicationContext 객체 저장소에 빈 객체로 등록
public class PerformanceInterceptor implements HandlerInterceptor { // 단일 책임 원칙 (SRP) - 하나의 메서드는 하나의 책임만 갖는다
    // Filter 구현에서 단일 책임원칙에 따라 메서드를 전처리와 후처리로 분리

    //long startTime; // iv - 인스턴스 변수, 싱글톤(객체1개)이라서 여러 쓰레드가 하나의 인스턴스 변수 공유. 값 덮여쓰여질수있음.
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {


        // 컨트롤러 호출하는 handler 만 전처리, 후처리 작업 하도록한다.
        if(!(handler instanceof HandlerMethod)) {
            return true;
        }

        // 1. 전처리 작업
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);



        // handler - 요청하고 연결된 컨트롤러 메서드
        HandlerMethod method = (HandlerMethod) handler; // ClassCastException 발생

        System.out.println("method.getMethod() = " + method.getMethod()); // URL하고 연결된 메서드
        System.out.println("method.getBean() = " + method.getBean());   // 메서드가 포함된 컨트롤러
        // return true; // 다음 인터셉터나 컨트롤러를 호출. false면 호출 안함.
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    // 2. 후처리 작업
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 전처리에서 작업한 값을 가져오기.
        long startTime = (long)request.getAttribute("startTime");

        long endTime = System.currentTimeMillis();
        System.out.print("[" + ((HttpServletRequest)request).getRequestURI() + "]");
        System.out.println(" time = " + (endTime - startTime));

        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
}

 

 

[해결책1]의 문제 : 소요시간 마지막에 /error요청이 preHandle에 걸림

/error 도 결국 컨트롤러 메서드 이므로 HandlerMethod이기 때문에 소요시간 측정이 된 것.

[/] time = 196 
[/.well-known/appspecific/com.chrome.devtools.json] time = 6 
[/css/main.css] time = 5 
method.getMethod() = public org.springframework.http.ResponseEntity org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(jakarta.servlet.http.HttpServletRequest) 
method.getBean() = org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController@77c929d2 [
/error] time = 23

 

[해결책1]의 문제 해결 : /error 경로 제외 등록

/error의 경우 정상 요청이 아니라 에러 응답 요청이기 때문에 이런 요청까지 인터셉터가 공통 전처리 및 후처리를 해버리면 성능 낭비가 된다.

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new PerformanceInterceptor())
                .addPathPatterns("/**") // 인터셉터를 적용할 대상
                .excludePathPatterns("/css/**", "/js/**", "/.well-known/**", "/error"); // 인터셉터 적용 제외 대상
    }
}

 

로그가 예쁘게 찍혔다.

 

깔끔한 실행 로그

 

 

해결책2 : interceptor에서 경로 제외

정적 리소스에 대한 요청은 제외한다.

단점 : 이미 아래와 같이 /css/** 경로는 제외하였음에도 발생한 에러임.

정적 경로가 css 말고도 더 있을 수도 있기 때문에 ClassCastException을 예방하기에 완벽하지는 않다.

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new PerformanceInterceptor())
                .addPathPatterns("/**") // 인터셉터를 적용할 대상
                .excludePathPatterns("/css/**", "/js/**"); // 인터셉터 적용 제외 대상
    }
}

'트러블슈팅' 카테고리의 다른 글

[Thymeleaf] 에러 해결 org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/index.html]")  (0) 2025.11.05
'트러블슈팅' 카테고리의 다른 글
  • [Thymeleaf] 에러 해결 org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/index.html]")
geologs
geologs
geologs 님의 블로그 입니다.
  • geologs
    geolog
    geologs
  • 전체
    오늘
    어제
    • 분류 전체보기 (20)
      • Artificial Intelligence (1)
        • Vibe Coding (0)
        • RAG (0)
      • Algorithm (10)
      • SpringBoot (0)
      • Network (0)
      • Architecture (0)
      • Design Pattern (1)
      • OpenSource Contribution (5)
      • 취준 (0)
      • 트러블슈팅 (2)
      • 자격증 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    preHandle
    코딩테스트
    opensource contribution
    SAA 단기 합격
    HandleMethod
    사과담기게임
    사각형칠하기
    오픈소스 빌드 환경 구성
    코드트리 #코딩테스트 #코테공부 #코테준비 #알고리즘공부 #갭체크
    Edit On Mode
    시뮬레이션1
    gradle wrapper
    claude code 설치
    Spring boot 로컬 빌드 환경 구성
    c++
    코테독학
    mavenCentral()
    개발자루틴
    Template Resolver
    spring boot
    AWS SAA 합격후기
    코테공부
    setting.gradle
    Plan Mode
    코드트리
    open source contribution
    ParseException
    TemplateInputException
    백준2828번
    Dispatcher Servelt
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
geologs
[Interceptor] java.lang.ClassCastException: class org.springframework.web.servlet.resource.ResourceHttpRequestHandler cannot be cast to class org.springframework.web.method.HandlerMethod (org.springframework.web.servlet.resource.ResourceHttpRequestHandler
상단으로

티스토리툴바