Spring Boot 배치(non-web) 애플리케이션에서 ‘missing ServletWebServerFactory bean’ 오류 해결하기

1. 문제 정의

Spring Boot로 만든 애플리케이션을 기동하려다 아래 예외를 만난 적이 있나요?

1
2
3
Unable to start web server; nested exception is
org.springframework.context.ApplicationContextException:
Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.

이 오류의 전형적인 시나리오는 Spring Batch 애플리케이션입니다. 로컬에서 클래스패스로 돌릴 때는 잘 되는데, 서버에서 아래처럼 시작하니 위 예외가 발생합니다.

1
java -cp jarFileName.jar; lib\* -Dlogging.level.org.springframework=DEBUG -Dspring.profiles.active=dev -Dspring.batch.job.names=abcBatchJob com.aa.bb.StartSpringBatch > somelogs.log

출처: Stack Overflow 질문 50231736 — ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean (2018-05 작성)

2. 빠른 진단 체크리스트

이 오류는 “목적이 웹이 아닌 앱인데 웹 서버를 띄우려다 실패"하는 전형적인 상황입니다. 흔히 발생하는 사례라는 점은 알지만, 정확한 발생 비율은 수치로 단정할 수 없습니다. 먼저 아래 체크리스트 순서로 상황을 좁히세요. 각 항목은 확인 명령과 함께 진행하며, ✅가 하나라도 걸리면 해당 항목을 해결하면 됩니다.

단계확인 항목확인 방법✅ 발견 시 액션
1이 앱은 정말 웹 서버가 필요한가? (배치/스케줄러/CLI 등)실행 목적 확인web-application-type: none 설정
2main 클래스에 @SpringBootApplication이 있나?소스 확인애노테이션 추가
3웹 앱이라면 내장 서버 스타터(spring-boot-starter-web)가 클래스패스에 있나?mvn dependency:treestarter 추가
4spring-boot-starter-tomcatscope=provided인가?pom.xml 확인scope 제거
5WebFlux(Reactive) 앱인가? 그런데 타입은 SERVLET으로 인식?사용 스타터 확인web-application-type: reactive
6의존성/local repo가 깨졌나?mvn clean install캐시 정리 후 재빌드

위 표의 각 항목에 대한 상세 원인 판별은 다음 섹션의 원인 후보 매트릭스에서 이어집니다.

3. 원인 후보와 배제 과정 (원인 후보 매트릭스)

에러 메시지는 “ServletWebServerFactory 빈이 없다"고 말하지만, 정확히는 Spring Boot가 애플리케이션 타입을 SERVLET으로 판단했는데, 이 타입에 필요한 서블릿 웹 서버 팩토리가 준비되지 않은 상태를 뜻합니다. 즉 서버가 웹으로 분류됐을 때만 이 빈이 필요해집니다. 아래 매트릭스로 원인을 좁히세요.

원인 후보확인 명령/방법맞는 경우의 증상해결 방향
A. 배치(non-web) 앱인데 웹 타입 인식됨실행 목적 + 클래스패스에 starter-web 존재 여부웹 서버 없이 배치만 돌려도 됨, 이 오류만 남spring.main.web-application-type: none
B. 웹 앱인데 @SpringBootApplication 누락main 클래스 소스 확인Application.run(...)인데 부트스트랩 애노테이션 없음@SpringBootApplication 추가
C. 웹 앱인데 내장 서버 스타터 없음mvn dependency:tree | grep tomcatstarter-web 제외/누락 상태spring-boot-starter-web 추가
D. starter-tomcatscope=providedpom.xml 확인빌드는 되는데 실행 시 내장 서버 못 찾음provided scope 제거
E. Reactive(WebFlux) 앱인데 타입 불일치사용 스타터(starter-webflux) 확인netty 사용, SERVLET 타입과 충돌spring.main.web-application-type: reactive
F. WAR 패키징 배포packaging 확인was 배포용인데 boot run으로 실행main이 SpringBootServletInitializer 상속 또는 web-application-type: none
G. Maven 로컬 repo 의존성 손상mvn clean install 실패반복 빌드 실패mvn dependency:purge-local-repository 후 재빌드

이번 사례(질문 본문)는 원인 A에 해당합니다. Spring Batch 앱이 클래스패스에 spring-boot-starter-web(또는 그로 인한 내장 톰캣)을 포함하고 있어서, Spring Boot가 시작 시 “웹 앱이구나"로 판단했지만 실제로는 배치만 수행하면 되는 앱이었습니다.

4. 해결 방법

4-1. 배치(non-web) 앱 — 웹 타입 끄기 (이번 사례의 정답)

가장 검증된 해결책은 애플리케이션 타입을 none으로 명시하는 것입니다. application.yml에:

1
2
3
spring:
  main:
    web-application-type: none

application.properties를 쓴다면:

1
spring.main.web-application-type=none

이 설정은 “이 애플리케이션은 웹 애플리케이션이 아니며, 임베디드 웹 서버를 띄우지 않는다"는 선언입니다. 이렇게 하면 Spring Boot가 SERVLET으로 잘못 분류하지 않으므로, ServletWebServerFactory 빈을 요구하지 않게 되어 배치 앱이 정상 기동됩니다. 원문에서 채택된 답변도 이 방식을 그대로 사용합니다.

4-2. @Bean으로 서블릿 웹 서버 팩토리를 직접 등록 — 비권장(실패에 가까운) 접근

ServletWebServerFactory 빈을 직접 등록해 우회하는 방법도 있습니다.

1
2
3
4
@Bean
public ServletWebServerFactory servletWebServerFactory() {
    return new TomcatServletWebServerFactory();
}

이 방식은 권장하지 않습니다. 명시적으로 ‘비권장/실패 접근’으로 보아야 하는 이유는 다음과 같습니다.

  • 이 코드는 클래스패스에 내장 톰캣/서블릿 컨테이너 구현이 있는 경우에만 동작하며, 빈을 강제로 채워 증상을 회피할 뿐 근본 원인(타입 오분류)을 제거하지 못합니다.
  • 배치 앱이라면 어차피 웹 서버가 필요 없는데, 굳이 서버 컨테이너를 띄우려 하므로 불필요한 포트 점유·리소스 낭비가 생기고, 특정 컨테이너 구현에 결합(coupling)됩니다.
  • web-application-type: none이 옳은 이유는 “웹 서버를 띄우지 않겠다"는 의도를 Spring Boot 설정값으로 명시하기 때문입니다. 이는 프레임워크가 공식 지원하는 방식이라 구현 클래스에 의존하지 않고, 이후 웹 스타터가 들어와도 배치 성격을 유지할 수 있습니다.

즉, @Bean 우회는 “올바른 해결책"이 아니라 임시방편에 가깝습니다. 배치 앱이라면 4-1의 web-application-type: none이 정석입니다.

4-3. 웹 앱이라면 — 스타터/타입 점검

  • spring-boot-starter-web 의존성이 클래스패스에 있는지, spring-boot-starter-tomcatprovided scope가 아닌지 확인합니다.
  • Reactive(WebFlux) 앱이라면 spring.main.web-application-type=reactive를 설정합니다.
  • WAR 배포 용도라면 main 클래스가 SpringBootServletInitializer를 상속받도록 합니다.
1
2
3
4
5
6
@SpringBootApplication
public class YourApplication extends SpringBootServletInitializer {
    public static void main(String[] args) {
        SpringApplication.run(YourApplication.class, args);
    }
}

5. 검증 환경 (이번 사례 기준)

이 문단의 버전 정보는 원문 질문(2018-05)에 명시된 정확한 Spring Boot/JDK/Maven 버전이 없어 추정임을 밝힙니다. 적용 환경에서 실제 버전과 비교해 확인하세요.

  • 원문 질문은 2018년 5월에 작성된 Spring Batch(Spring Boot) 앱 사례로, 로컬(클래스패스)에서는 동작하고 리눅스 서버의 java -cp ... -Dspring.batch.job.names=... 실행에서만 실패했습니다.
  • spring.main.web-application-type 설정값(none/servlet/reactive)은 Spring Boot 2.0 이후에서 도입된 설정입니다. 따라서 이 해결책은 Spring Boot 2.x 국면의 설정이며, 원문 인용 시점(2018년) 기준의 Spring Boot 환경을 전제합니다. (추정)
  • 실행 방식(명령줄의 -D 시스템 프로퍼티, application.yml)으로 볼 때 Java + Maven 기반의 Spring Batch 프로젝트임을 유추할 수 있습니다만, 빌드 도구·JDK 구체 버전은 원문에 명시되어 있지 않아 단정하지 않습니다. (추정)

재현에 직접 사용한 환경은 없으므로, 아래 해결책은 원문의 채택 답변 내용을 기준으로 정리한 것입니다. 로컬에서 Spring Boot 배치 앱을 만들고 같은 예외를 재현해 검증하는 것을 권장합니다.

6. 버전별 차이 (Spring Boot 1.x / 2.x / 3.x)

이 섹션의 버전별 동작은 원문 출처에 없는 내용으로, 공식 문서 기준의 일반적 동작을 바탕으로 한 추정입니다. 반드시 사용 중인 Spring Boot 버전의 문서와 실제 동작으로 확인하세요.

Spring Boot가 애플리케이션 타입(웹 여부)을 인식하고 web-application-type 설정을 처리하는 방식은 버전에 따라 다릅니다.

Spring Boot웹 타입 인식 방식spring.main.web-application-type 지원배치 앱 해결책
1.xspring.main.web-environment(boolean)로 웹 환경 켜기/끄기해당 설정 미지원 (2.0부터) (추정)1.x에서는 web-application-type 대신 별도 설정/자동설정 배제 필요 (추정)
2.x클래스패스에서 spring-webmvc/spring-webflux 존재로 SERVLET/REACTIVE 자동 판단, spring.main.web-application-type: none 명시 가능지원 (2.0 ~ 2.x)이번 사례 정답. spring.main.web-application-type: none
3.x2.x와 동일한 자동 타입 판단 + Jakarta EE 기반 (javax→jakarta 전환) (추정)지원 (추정)동일하게 spring.main.web-application-type: none 적용 가능 (추정)

핵심 차이는 다음과 같습니다.

  1. 속성 이름: 1.x의 spring.main.web-environment(boolean)는 2.0부터 spring.main.web-application-type(none/servlet/reactive)으로 대체되었습니다. 그러나 직접적인 실행 확인이 없어 이것도 추정입니다.
  2. 자동 판단 근거: 2.x/3.x은 클래스패스의 웹 스타터(web/WebFlux) 여부로 기본 타입을 정하는데, 배치 앱에 웹 스타터가 섞여 있으면 SERVLET으로 잘못 분류되어 본 오류가 납니다. 이 원리는 해당 버전들의 공통 동작으로 보입니다. (추정)
  3. Jakarta 전환: 3.x은 javax 대신 jakarta 패키지를 사용하므로, 옛 샘플 코드를 그대로 붙여넣으면 컴파일 오류가 먼저 발생할 수 있습니다. 3.x 기준으로 정리된 예제인지 확인하세요. (추정)

이 글의 핵심 해결책(web-application-type: none)은 2.x 이상에서 동작하는 설정입니다. 1.x를 사용 중이라면 동일한 속성이 지원되지 않을 수 있으니, 사용 중인 버전의 공식 문서를 먼저 확인해야 합니다.

7. 검증 명령

수정 후 정상 동작 여부를 확인하는 명령입니다.

1
2
3
4
5
6
7
8
# 배치 앱: web-application-type: none 적용 후 기동 → "Started ... in X seconds" 로그 확인
java -cp jarFileName.jar; lib\* -Dspring.batch.job.names=abcBatchJob com.aa.bb.StartSpringBatch

# 의존성 트리에서 내장 서버 스타터 존재 확인
mvn dependency:tree | grep -i tomcat

# 전체 재빌드 후 실행 (로컬/서버 공통)
mvn clean package && java -jar target/app.jar

배치 앱이라면 아무 웹 서버 포트를 열지 않고 배치 잡이 종료되는지(또는 대기 상태로 유지되는지)를 확인합니다. Spring Boot 기동 완료 로그 Started <Application> in X.XXX seconds가 출력되면 성공입니다.

8. 재발 방지 체크

  • 배치/스케줄러/CLI 앱은 처음부터 spring.main.web-application-type: none을 프로파일 기본값으로 비워두지 말고 명시하세요. 나중에 실수로 인해 웹 스타터가 들어와도 오류 없이 안정적으로 시작됩니다.
  • 웹 앱은 내장 서버 스타터와 @SpringBootApplication 유무, 패키징 타입(WAR/JAR)을 pom.xml 수정 후 곧바로 mvn dependency:tree로 확인하세요.
  • 로컬에서는 되는데 서버/CI에서만 실패하는 경우에는 클래스패스 차이(내장 톰캣이 빠지는 줄바꿈이나 provided scope)를 먼저 의심합니다.
  • CI 파이프라인에서 이 오류가 반복된다면 mvn dependency:purge-local-repository로 의존성 손상 여부까지 체크하는 단계를 추가합니다.
  • 버전 확인: web-application-type 설정은 2.x 이상에서만 지원되므로, 사용 중인 Spring Boot 버전이 1.x인지 먼저 확인하세요(위 6절 참고).

9. DevTrace 독자 결론

이 메시지가 “웹 서버 팩토리 빈이 없다(cannot find ServletWebServerFactory)“라고 말할 때, 대부분의 배치 개발자는 웹 설정이 필요하다고 오해하기 쉽습니다. 핵심은 **“팩토리가 없어서"가 아니라 “Spring Boot가 이 앱을 웹(SERVLET)으로 잘못 분류했다”**는 데 있습니다. 웹이 필요 없는 배치 앱이라면 spring.main.web-application-type: none 한 줄이면 문제가 끝납니다.

일반적인 점검 기준으로, 위 표의 일부 항목(B/D/G)은 빌드 도구·의존성 버전·패키징 환경에 따라 증상이 달라질 수 있습니다. 적용 전 우선순위는 “이 앱이 정말 웹을 필요로 하는가"부터 판단하세요. 버전 정보(6절)는 원문 출처에 없는 내용으로 추정이며, 사용 중인 버전 공식 문서로 재확인하세요.


출처: Stack Overflow — ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean