/개발/Spring Boot 기동 실패 UnsatisfiedDependencyException 원인 6가지 해결법
개발SpringBootUnsatisfiedDependencyException

Spring Boot 기동 실패 UnsatisfiedDependencyException 원인 6가지 해결법

Spring Boot가 UnsatisfiedDependencyException, No qualifying bean of type 오류로 기동조차 안 될 때 스택트레이스를 거꾸로 읽어 6가지 원인을 5분에 특정하는 진단법과 복붙 수정 코드를 정리했습니다.

Spring Boot 기동 실패 UnsatisfiedDependencyException 원인 6가지 해결법

Spring Boot 기동 실패! UnsatisfiedDependencyException 원인 6가지 5분 진단

어제까지 잘 뜨던 앱이 오늘 아침 빨간 스택트레이스 200줄과 함께 죽었습니다. Error creating bean with name ..., No qualifying bean of type ... available. 화면 가득한 nested exception을 보면 심장이 철렁하지만, 결론부터 말씀드리면 당황하지 말고 맨 아래 한 줄부터 읽으면 됩니다.

이 글은 JWT 인증 실패나 Whitelabel 404 같은 '떠 있는 앱의 런타임 에러'가 아니라, 애플리케이션이 기동 자체에 실패하는 빈(Bean) 등록·주입 단계 문제에 한정해 다룹니다. 이 단계의 실패 패턴은 사실상 6가지뿐이고, 원인만 특정하면 대부분 한 줄로 해결됩니다.

스택트레이스를 거꾸로 읽는 법 (ssh -vvv처럼)

스택트레이스는 위에서부터 읽으면 길을 잃습니다. ssh -vvv 로그 디버깅처럼 맨 아래부터 거꾸로 읽어야 진짜 원인이 보입니다. 실제 예시를 봅시다.

CODE
Error creating bean with name 'orderController':       ← ① 누가: orderController 생성 중
  Unsatisfied dependency expressed through constructor parameter 0;  ← ② 무엇을: 생성자 0번 파라미터 주입 실패
  nested exception is
  org.springframework.beans.factory.NoSuchBeanDefinitionException:
  No qualifying bean of type 'com.example.PaymentService' available:  ← ③ 왜: PaymentService 빈이 없음
  expected at least 1 bean which qualifies as autowire candidate

여기서 3요소만 뽑으면 끝납니다.

  1. 누가 (생성 실패한 빈): orderController
  2. 무엇을 (주입받으려던 대상): 생성자 파라미터 0번
  3. (근본 원인): NoSuchBeanDefinitionExceptionPaymentService 빈이 컨테이너에 없음

맨 아랫줄 예외 타입에 따라 원인 방향이 갈립니다.

맨 아래 예외 메시지의미의심 원인
NoSuchBeanDefinition: No qualifying bean ... expected at least 1빈이 0개컴포넌트 누락 / 스캔 범위 밖 / 조건부 빈 미생성
NoUniqueBeanDefinitionException: expected single matching bean but found 2빈이 2개 이상같은 타입 다중 등록, @Qualifier 필요
Requested bean is currently in creation / circular reference서로를 참조생성자 순환참조

원인 6가지 진단 표 + 복붙 해결 코드

먼저 한눈에 보는 매핑 표입니다.

#에러 단서원인해결책
1No qualifying bean ... expected at least 1@Component 누락 또는 스캔 범위 밖어노테이션 부착 / @ComponentScan(basePackages=...)
2expected single matching bean but found 2구현체 다중@Qualifier / @Primary
3currently in creation생성자 순환참조설계 분리 / @Lazy
4빈이 통째로 없음@ConditionalOnProperty·@Profile 미충족프로퍼티/프로파일 활성화
5NoUniqueBeanDefinitionException동일 타입 빈 2개@Primary 또는 명시 주입
6테스트에서만 실패슬라이스 컨텍스트에 빈 누락@MockBean / 슬라이스 범위 조정

원인 1. 컴포넌트 누락 또는 스캔 범위 밖

가장 흔합니다. @SpringBootApplication은 자신이 위치한 패키지와 그 하위만 스캔합니다. com.example.common 같은 별도 루트에 빈이 있으면 못 잡습니다.

JAVA
@SpringBootApplication
@ComponentScan(basePackages = {"com.example.app", "com.example.common"})
public class Application { }

물론 클래스에 @Service/@Component 자체를 빠뜨린 경우도 많으니 먼저 확인하세요.

원인 2 & 5. 구현체 다중 → @Qualifier / @Primary

PaymentService 구현체가 KakaoPayService, NaverPayService 둘이면 스프링은 무엇을 주입할지 몰라 NoUniqueBeanDefinitionException을 던집니다.

JAVA
@Service @Qualifier("kakaoPay")
public class KakaoPayService implements PaymentService { }

@Service @Primary  // 기본 후보 지정
public class NaverPayService implements PaymentService { }

// 주입 시 명시 선택
public OrderController(@Qualifier("kakaoPay") PaymentService paymentService) { ... }

원인 3. 생성자 순환참조 → @Lazy

A가 B를, B가 A를 생성자로 주입하면 순환참조입니다. Spring Boot 2.6+ 부터 spring.main.allow-circular-references 기본값이 false라 기동이 곧바로 막힙니다(과거 필드 주입으로 숨어 있던 문제가 생성자 주입 권장으로 조기에 드러나는 셈입니다). 정석은 책임 분리지만, 급할 땐:

JAVA
public AService(@Lazy BService bService) {
    this.bService = bService;
}

원인 4. 조건부 빈 미생성

@ConditionalOnProperty 조건이 안 맞으면 빈 자체가 안 만들어집니다.

JAVA
@Bean
@ConditionalOnProperty(name = "feature.pay.enabled", havingValue = "true")
public PaymentService paymentService() { ... }

application.ymlfeature.pay.enabled: true를 넣어야 빈이 생깁니다. @Profile("prod")도 마찬가지입니다.

원인 6. 테스트에서만 실패

@WebMvcTest는 컨트롤러 슬라이스만 로딩하므로 서비스 빈이 없습니다. 의존 빈은 @MockBean으로 채워주세요.

JAVA
@WebMvcTest(OrderController.class)
class OrderControllerTest {
    @MockBean PaymentService paymentService;
    @Autowired MockMvc mockMvc;
}

실무 한마디

저는 신규 프로젝트 셋업 직후 가장 먼저 패키지 구조를 com.example.app 루트 하나로 정렬합니다. 멀티 루트 패키지에서 basePackages를 빠뜨려 발생하는 원인 1번이 전체 빈 주입 이슈의 절반 이상을 차지하더군요. 그리고 자동설정 빈이 안 생길 땐 라이브러리 코드가 아니라 build.gradle 의존성 누락을 먼저 의심하는 게 빠릅니다.

흔한 함정 & 검증 루틴

  • 멀티모듈: core 모듈의 빈이 api 모듈에서 안 잡히면, 의존성과 함께 스캔 경로(@ComponentScan)에 그 패키지가 포함됐는지 확인하세요.
  • 의존성 누락으로 자동설정 빈 미생성: 라이브러리가 없으면 @ConditionalOnClass 자동설정이 통째로 비활성화됩니다.
GROOVY
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
  • @Autowired(required=false)·Optional 우회의 함정: 에러는 사라지지만 런타임에 NPE로 더 늦게 터집니다. 근본 원인을 가리는 임시방편일 뿐입니다.
  • Actuator로 실제 등록 빈 확인: 추측 대신 눈으로 봅시다.
PROPERTIES
management.endpoints.web.exposure.include=beans

/actuator/beans를 호출해 문제의 빈 이름이 목록에 있는지 확인하면 "스캔이 안 된 건지, 조건 때문에 안 만들어진 건지"가 즉시 갈립니다. 참고로 Spring Boot 3.x는 Jakarta 네임스페이스(jakarta.*) 전환 후라 javax.* 기반 라이브러리는 빈 스캔/자동설정에서 누락될 수 있고, GraalVM Native Image에서는 리플렉션 기반 빈 등록이 빌드타임에 빠질 수 있어 별도 힌트가 필요합니다.

결론: 진단 체크리스트 1장 요약

  1. 스택트레이스 맨 아래부터 읽어 예외 타입 확인
  2. No qualifying bean → 컴포넌트 누락/스캔 범위/조건부 빈
  3. expected single ... found 2@Qualifier/@Primary
  4. currently in creation → 순환참조, 설계 분리 또는 @Lazy
  5. 테스트만 실패 → 슬라이스 범위 + @MockBean
  6. 헷갈리면 /actuator/beans로 실제 등록 여부 확인

"빨간 줄 200개"는 사실 맨 아래 한 줄을 위한 호위무사일 뿐입니다. 거꾸로 읽기를 습관화하고, 평소 Actuator로 빈 목록을 사전 점검하는 루틴을 들이면 기동 실패는 더 이상 무섭지 않습니다.

자주 묻는 질문 (FAQ)

Q. @Component를 붙였는데도 No qualifying bean이 뜹니다. A. 거의 100% 스캔 범위 밖입니다. 해당 클래스 패키지가 @SpringBootApplication이 위치한 패키지의 하위가 아니라면 @ComponentScan(basePackages=...)에 명시하세요.

Q. @Repository는 인식되는데 제 클래스만 안 됩니다. A. 그 Repository는 자동설정/스캔 범위 안에 있고 여러분 클래스만 밖이라는 신호입니다. 패키지 위치와 어노테이션 부착 여부를 함께 확인하세요.

Q. 테스트에서만 빈 주입이 실패합니다. A. @WebMvcTest·@DataJpaTest 같은 슬라이스는 일부 빈만 로딩합니다. 필요한 의존성은 @MockBean으로 채우거나, 전체 컨텍스트가 필요하면 @SpringBootTest를 쓰세요.

Q. 순환참조인지 어떻게 알 수 있나요? A. 메시지에 currently in creation 또는 circular reference가 보이면 확정입니다. Spring Boot 2.6+는 기본적으로 막으므로 기동 즉시 드러납니다.

Q. 구현체가 2개일 때 무조건 @Primary를 써도 되나요? A. "기본 구현이 명확히 하나"일 때만 @Primary가 적절합니다. 상황별로 골라 써야 한다면 주입 지점마다 @Qualifier로 명시하는 편이 안전하고 의도가 분명합니다.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.

작성 · Content Reviewer·검토 · 사람 편집자·발행 · 2026년 6월 15일

댓글

불러오는 중...