개인 프로젝트에서 기존 STOMP 기반 WebSocket 채팅을 제거하고, Kafka 기반 실시간 채팅 시스템으로 마이그레이션을 진행했습니다. 이 과정에서 WebSocket 연결 시도 시 404 오류가 발생하는 문제가 있어서 저와 같이 30분 시간 낭비 안하시길 바라면서 포스팅 합니다.
변경 배경: 왜 STOMP에서 Kafka로?
기존 STOMP 방식은 Spring에서 빠르게 WebSocket 채팅을 구현할 수 있었지만, 다음과 같은 한계가 있었습니다.
- 단일 WAS 한정 메시지 브로커(SimpleBroker) 사용
- 서버 인스턴스가 여러 대일 때 세션 동기화 어려움
- 대량 트래픽 처리 시 확장성 부족
- 메시지 전달 경로가 김 (WebSocket -> SimpleBroker -> Conusumer -> 다시 WebSocket
또한 SimpleBroker 의 경우 메모리 기반이라 서버가 늘어나면 세션 동기화 문제 등 여러 문제가 생깁니다.
변경 후 구조 : Kafka 단독
Client ⇄ WebSocket(ChatWebSocketHandler) ⇄ Kafka ⇄ Consumer
이 구조 변경의 이점은
- 메시지 전달 경로 단순화
- Kafka가 WebSocket메시지를 직접 처리하기 때문에 SimpleBroker 단계가 사라지고 지연 시간이 줄었습니다.
- 확장성과 안정성 강화
- Kafka는 기본적으로 클러스터링, 파티셔닝, 컨슈머 그룹으로 대량 트래픽을 처리합니다. 따라서 수만명이 동시에 접속해도
부하를 여러 서버에 분산 가능하며, STOMP처럼 단일 서버 메모리 의존이 아닙니다.
- Kafka는 기본적으로 클러스터링, 파티셔닝, 컨슈머 그룹으로 대량 트래픽을 처리합니다. 따라서 수만명이 동시에 접속해도
- 관리 포인트 감소
- WebSocket 메시징과 내부 메시징 시스템을 Kafka로 통일함으로써 운영 관리 및 코드유지보수의 이점이 있습니다.
해당 변경을 하면서 문제 상황이 발생했습니다.
프론트 엔드 콘솔에서
WebSocket connection to 'ws://localhost:8080/ws/chat?token=...' failed:
WebSocket 오류: Event {isTrusted: true, type: 'error', ...}
WebSocket 연결 종료
Spring 서버 로그에서는
WARN --- o.s.web.servlet.PageNotFound : No mapping for GET /ws/chat WARN --- ExceptionHandlerExceptionResolver : Resolved [org.springframework.web.servlet.NoHandlerFoundException: No endpoint GET /ws/chat.]
에러가 발생 했습니다 해당에러는
Spring Boot 3.x에서는 WebSocket 요청도 DispatcherServlet을 거쳐 처리됩니다.
기존 STOMP 설정에서는 @EnableWebSocketMessageBroker가 자동으로 WebSocketHandlerMapping을 등록했지만, Kafka 구조로 바꾸면서 이 어노테이션이 빠져서 생긴 문제였습니다. (낮은버전 GPT 써도 30분 날림)
결과적으로
WebSocket 요청 → DispatcherServlet → 매핑 없음 → 404 발생 이 발생했습니다.
해결 방법으로는
WebSocketConfig 클래스에 @EnableWebSocket 어노테이션 추가.


뭐가 달라지신지 보이시나요? @EnableWebSocket 어노테이션 누락만 추가하니 간단하게 해결했습니다 ^^
처음엔 SecurityConfig, JwtFilter, WebMvcConfig까지 모두 의심했지만결국 문제는 단 하나, @EnableWebSocket의 누락입니다..
이글을 누군가 보신다면 GPT도 가끔 실수해서 추가 안해줄수 있으니 검색해서 시간 아끼시길 바랍니다
Spring Boot 3에서 STOMP → Kafka 전환 시 WebSocketConfig에 @EnableWebSocket 필수.
'Java > Spring' 카테고리의 다른 글
| Kafka Listener 예외 반복 실행되는 이유 그리고 해결 (1) | 2025.08.15 |
|---|---|
| 전략 패턴에서 Object vs 제네릭, 왜 나는 제네릭을 선택했을까? (2) | 2025.07.30 |
| Spring에서 확장성을 고려한 채팅방 생성 로직 전략 패턴 적용 (1) | 2025.07.24 |
| Spring Boot에서 application.yml 설정 분리하기 (0) | 2025.07.20 |
| 테스트에서 환경변수를 주입하는 가장 가벼운 방법 (0) | 2025.05.08 |