[Java/Spring Boot] "No thread-bound request found" 에러 원인과 해결법

2025. 9. 19. 23:26·개발 (Development)/Java

이 에러는 현재 실행 중인 스레드에 웹 요청(HttpServletRequest) 정보가 바인딩되어 있지 않다는 의미입니다. 주로 비동기 작업(@Async)이나 백그라운드 스레드에서 Spring MVC의 요청 컨텍스트에 접근하려 할 때 발생합니다.

🔍 에러 원인과 시스템에 미치는 영향

이 에러는 스레드의 경계를 넘나드는 요청 정보 접근 때문에 발생합니다. Spring MVC는 웹 요청을 처리하는 스레드에 HttpServletRequest와 같은 데이터를 연결합니다. 하지만 @Async나 new Thread()로 새로운 스레드를 생성하면, 이 스레드는 기존의 요청 정보를 공유하지 않습니다. 따라서 새로운 스레드에서 RequestContextHolder를 사용해 요청 정보에 접근하려 할 때 에러가 발생합니다.

이 에러는 단순한 로그 기록을 넘어 시스템에 다음과 같은 영향을 미칠 수 있습니다.

  1. 기능 오류: 사용자 세션 정보나 인증/인가 데이터(예: 헤더의 토큰)에 접근해야 하는 비동기 작업이 제대로 수행되지 않아 서비스 기능 자체가 멈추거나 잘못된 결과를 반환할 수 있습니다.
  2. 데이터 무결성 문제: 요청과 관련된 특정 데이터를 처리하는 비동기 작업이 실패하면, DB에 데이터가 부분적으로만 저장되거나, 불완전한 상태로 남는 등 데이터 무결성 문제가 발생할 수 있습니다.
  3. 성능 저하 및 자원 낭비: 에러가 반복적으로 발생하면 애플리케이션 로그에 수많은 스택 트레이스(Stack Trace)가 쌓여 디스크 공간을 빠르게 채우고, 불필요한 예외 처리 로직으로 인해 CPU 자원을 낭비할 수 있습니다. 이는 시스템 전반의 성능 저하로 이어집니다.
  4. 사용자 경험 악화: 요청을 비동기로 처리하는 동안 에러가 발생하면, 사용자는 요청이 실패했는지 알 수 없거나, 예상치 못한 오류 페이지를 마주하게 되어 서비스에 대한 신뢰도를 잃을 수 있습니다.

💡 해결 방안: 요청 컨텍스트 전파하기

이 문제의 핵심은 비동기 스레드에게 부모 스레드의 요청 정보를 넘겨주는 것입니다.

1. @Async 사용 시 TaskExecutor 설정 (가장 추천)

Spring Boot에서 @Async를 사용한다면, TaskExecutor를 직접 구성하여 요청 컨텍스트를 자동으로 전파하도록 설정하는 것이 가장 안정적입니다. TaskDecorator를 이용해 부모 스레드의 요청 컨텍스트를 자식 스레드로 복사하고, 작업이 끝나면 정리하는 로직을 추가합니다.

예시 코드:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.setThreadNamePrefix("MyAsync-");

        executor.setTaskDecorator(new TaskDecorator() {
            @Override
            public Runnable decorate(Runnable runnable) {
                RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
                return () -> {
                    try {
                        RequestContextHolder.setRequestAttributes(requestAttributes);
                        runnable.run();
                    } finally {
                        RequestContextHolder.resetRequestAttributes();
                    }
                };
            }
        });

        executor.initialize();
        return executor;
    }
}

이제 @Async를 사용할 때 taskExecutor 빈을 지정해주기만 하면 됩니다.

@Async("taskExecutor")
public void someAsyncMethod() {
    // 이제 이 메서드 안에서도 RequestContextHolder에 접근 가능!
    String sessionId = RequestContextHolder.currentRequestAttributes().getSessionId();
    // ...
}

2. 필요한 데이터만 직접 전달하기

비동기 메서드에서 일부 데이터(예: 특정 헤더, 세션 ID)만 필요한 경우, HttpServletRequest 객체 전체를 넘기는 대신 필요한 데이터만 추출해서 파라미터로 전달하는 것이 좋습니다. 이 방법은 스레드 안정성 문제로부터 자유롭고 코드가 명확해집니다.

public String someControllerMethod(HttpServletRequest request) {
    String authorizationHeader = request.getHeader("Authorization");
    myService.myAsyncMethod(authorizationHeader);
    return "OK";
}

@Async
public void myAsyncMethod(String authorizationHeader) {
    // 이제 authorizationHeader를 바로 사용할 수 있습니다.
    // ...
}
반응형

'개발 (Development) > Java' 카테고리의 다른 글

[Java] Object를 특정 클래스 타입으로 변환  (0) 2025.11.28
[Java] 객체 리스트를 특정 속성으로 정렬하는 방법  (2) 2025.10.06
[Java/Spring Boot] JdbcTemplate으로 여러 데이터베이스 연결하기  (0) 2025.09.14
[Java/Spring Boot] Spring Boot에서 OAuth2 인증 401 오류 해결하기  (0) 2025.09.14
[Java] JPA에서 DTO는 Interface로 구현할까? Class로 구현할까?  (4) 2025.08.03
'개발 (Development)/Java' 카테고리의 다른 글
  • [Java] Object를 특정 클래스 타입으로 변환
  • [Java] 객체 리스트를 특정 속성으로 정렬하는 방법
  • [Java/Spring Boot] JdbcTemplate으로 여러 데이터베이스 연결하기
  • [Java/Spring Boot] Spring Boot에서 OAuth2 인증 401 오류 해결하기
LoopThinker
LoopThinker
모르는 것을 알아가고, 아는 것을 더 깊게 파고드는 공간
  • LoopThinker
    CodeMemoir
    LoopThinker
  • 전체
    오늘
    어제
    • 분류 전체보기 (237)
      • 개발 (Development) (170)
        • Algorithm (1)
        • Angular (1)
        • AWS (7)
        • DeepSeek (2)
        • Docker (7)
        • Git (3)
        • Java (36)
        • JavaScript (4)
        • Kafka (5)
        • Kubernetes (4)
        • Linux (7)
        • PostgreSQL (38)
        • Python (33)
        • React (3)
        • TypeScript (3)
        • Vue.js (5)
        • General (11)
      • 데이터 분석 (Data Analysis) (1)
      • 알고리즘 문제 풀이 (Problem Solving.. (27)
      • 자격증 (Certifications) (24)
        • ADsP (14)
        • 정보처리기사 (4)
        • Linux Master (5)
        • SQLD (1)
      • 기술 동향 (Tech Trends) (12)
      • 기타 (Others) (3)
  • 블로그 메뉴

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

  • 공지사항

  • 인기 글

  • 태그

    ADsP
    JSON
    백준알고리즘
    백준
    Kubernetes
    Vue.js
    리눅스 마스터 2급 2차
    DevOps
    timescaledb
    MyBatis
    데이터분석
    자바
    javascript
    리눅스 마스터 2급
    java
    python
    JPA
    파이썬
    Linux master
    pandas
    오답노트
    백준자바
    Kafka
    AWS
    백준온라인저지
    docker
    PostgreSQL
    Linux
    springboot
    deepseek
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
LoopThinker
[Java/Spring Boot] "No thread-bound request found" 에러 원인과 해결법
상단으로

티스토리툴바