Maven BOM이란? dependencyManagement와 Spring Boot 의존성 버전 관리 이해하기

궁금하면 500원·2025년 7월 4일

Java

목록 보기
13/15

Maven BOM과 dependencyManagement 이해하기

Spring Boot 프로젝트의 pom.xml을 보다 보면 의존성에 버전이 없는 경우가 많다.

예를 들어 다음과 같은 코드다.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
</dependency>

일반적인 Maven 프로젝트라면 어떤 버전의 spring-web을 사용할 것인지 지정해야 할 것 같은데, Spring Boot에서는 버전을 생략해도 정상적으로 빌드된다.

그 이유 중 하나가 BOM 이다.


1. BOM이란?

BOM은 여러 라이브러리의 버전을 하나의 POM에서 중앙 관리하기 위한 특별한 형태의 POM이다.

예를 들어 Spring Boot는 다음과 같은 수많은 라이브러리를 함께 사용한다.

Spring Framework
Jackson
Tomcat
Hibernate
Logback
Micrometer
JUnit
Netty
Reactor

이 라이브러리들은 각각 독립적으로 버전이 올라간다.

문제는 무조건 최신 버전을 조합한다고 해서 정상적으로 동작하는 것이 아니라는 점이다.

예를 들어 임의로 다음처럼 구성했다고 하자.

Spring Framework 6.x
Jackson 2.x
Hibernate 7.x
Tomcat 11.x

각 라이브러리 자체는 정상적인 버전이어도 서로 호환되지 않을 수 있다.

그래서 Spring Boot는 자신이 테스트한 라이브러리 버전 조합을 하나의 BOM으로 제공한다.

대표적인 것이 다음 POM이다.

org.springframework.boot:spring-boot-dependencies

즉 BOM의 핵심 역할은 다음과 같다.

라이브러리 A → 1.2.3
라이브러리 B → 4.5.6
라이브러리 C → 7.8.9
라이브러리 D → 2.1.0

서로 함께 사용할 수 있는 버전 조합을 중앙에서 정의하는 것이다.


2. dependencyManagement와 BOM은 같은 것이 아니다

여기서 자주 헷갈리는 부분이 있다.

<dependencyManagement>

와 BOM은 같은 개념이 아니다.

dependencyManagement는 Maven에서 의존성 버전과 관련 설정을 관리하는 기능이다.

반면 BOM은 그곳에 가져와 사용할 수 있는 버전 정책이 정의된 POM이다.

정리하면 다음 관계다.

dependencyManagement
        ↓
의존성 버전 관리 기능

BOM
        ↓
여러 의존성 버전 정보를 모아놓은 POM

따라서 BOM은 보통 dependencyManagement 안에서 import해서 사용한다.


3. BOM을 import하는 방법

Spring Boot BOM을 직접 가져온다면 다음과 같이 작성할 수 있다.

<dependencyManagement>
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.x.x</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

    </dependencies>
</dependencyManagement>

여기서 중요한 부분은 두 가지다.

<type>pom</type>
<scope>import</scope>

type=pom은 해당 의존성이 일반적인 JAR 라이브러리가 아니라 POM 자체라는 의미다.

scope=import는 해당 POM의 dependencyManagement 정보를 현재 프로젝트로 가져오겠다는 의미다.

흐름으로 보면 다음과 같다.

spring-boot-dependencies BOM
            ↓
라이브러리 버전 정책 정의
            ↓
dependencyManagement에서 import
            ↓
현재 프로젝트의 버전 정책으로 사용

4. BOM을 가져왔다고 라이브러리가 설치되는 것은 아니다

이 부분도 중요하다.

BOM을 import했다고 해서 BOM에 들어 있는 모든 라이브러리가 프로젝트에 추가되는 것은 아니다.

예를 들어 BOM 안에 다음 버전들이 정의되어 있다고 가정하자.

spring-web → 6.x.x
jackson-databind → 2.x.x
hibernate-core → 6.x.x
junit-jupiter → 5.x.x

BOM은 단순히 다음 정보를 제공한다.

"spring-web을 사용한다면 이 버전을 사용해라."
"Jackson을 사용한다면 이 버전을 사용해라."

실제 라이브러리를 사용하려면 여전히 <dependencies>에 선언해야 한다.

<dependencies>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
    </dependency>

</dependencies>

즉 다음 두 가지는 역할이 완전히 다르다.

dependencyManagement
    ↓
버전과 의존성 정책 관리

dependencies
    ↓
실제 프로젝트에서 사용할 의존성 선언

dependencyManagement에 있다고 자동으로 클래스패스에 포함되는 것은 아니다.


5. 버전을 생략할 수 있는 이유

BOM에서 spring-web의 버전을 관리하고 있다면 실제 의존성에서는 다음처럼 버전을 생략할 수 있다.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
</dependency>

Maven은 dependencyManagement에서 해당 라이브러리의 버전을 찾는다.

개념적으로는 다음과 같다.

dependencies

spring-web
버전 없음
    │
    ▼
dependencyManagement 확인
    │
    ▼
spring-web 버전 발견
    │
    ▼
해당 버전 적용

덕분에 프로젝트 전체에 다음과 같은 코드가 반복되는 것을 줄일 수 있다.

<version>...</version>

6. Spring Boot에서는 어떻게 적용될까?

Spring Boot 프로젝트에서는 흔히 다음 Parent를 사용한다.

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.x.x</version>
    <relativePath/>
</parent>

spring-boot-starter-parent는 Spring Boot에서 권장하는 여러 Maven 설정을 제공하며, 그 과정에서 spring-boot-dependencies의 의존성 관리 정책도 사용할 수 있게 해준다.

그래서 일반적인 Spring Boot 프로젝트에서는 개발자가 직접 다음 BOM을 import하지 않아도 된다.

<artifactId>spring-boot-dependencies</artifactId>

그 결과 다음과 같은 의존성을 자연스럽게 작성할 수 있다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

버전을 적지 않아도 Spring Boot에서 관리하는 버전이 적용된다.


7. Parent를 사용하지 않고 BOM만 사용할 수도 있다

모든 프로젝트가 spring-boot-starter-parent를 사용할 수 있는 것은 아니다.

회사에서 이미 자체 Parent POM을 사용하고 있을 수도 있다.

예를 들어 다음과 같은 구조가 있을 수 있다.

<parent>
    <groupId>com.example</groupId>
    <artifactId>company-parent</artifactId>
    <version>1.0.0</version>
</parent>

Maven 프로젝트는 하나의 Parent만 지정할 수 있기 때문에 이 경우 Spring Boot Parent를 동시에 사용할 수 없다.

이럴 때 Spring Boot BOM만 가져오는 방법을 사용할 수 있다.

<dependencyManagement>
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.x.x</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

    </dependencies>
</dependencyManagement>

구조는 다음과 같다.

회사 Parent POM
      +
Spring Boot BOM
      ↓
회사 공통 Maven 설정 유지
      +
Spring Boot 의존성 버전 관리 사용

실무에서도 충분히 볼 수 있는 구조다.


8. BOM을 사용하다가 직접 버전을 지정하면?

예를 들어 BOM에서는 특정 버전의 Jackson을 관리하고 있다고 하자.

하지만 프로젝트에서 직접 다음처럼 버전을 선언할 수도 있다.

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>...</version>
</dependency>

이 경우 직접 선언한 버전이 사용될 수 있다.

하지만 특별한 이유 없이 이렇게 BOM의 버전 정책을 벗어나기 시작하면 주의해야 한다.

Spring Boot가 제공하는 BOM은 단순히 최신 버전을 모아둔 목록이 아니라 함께 동작하도록 조합하고 검증한 버전 세트이기 때문이다.

따라서 특정 라이브러리의 버전을 강제로 변경한다면 다음을 확인해야 한다.

Spring Boot와 호환되는가?
Spring Framework와 호환되는가?
다른 라이브러리와 충돌하지 않는가?
보안 패치 때문에 변경하는 것인가?
테스트 환경에서 충분히 검증했는가?

버전을 바꿀 수 있다는 것과 바꾸는 것이 안전하다는 것은 다른 문제다.


9. BOM과 Maven의 의존성 충돌 해결

Maven에서는 하나의 라이브러리에 여러 버전이 들어오면 최종적으로 하나의 버전을 선택해야 한다.

예를 들어 다음 구조가 있다고 하자.

my-app
 ├─ library-a
 │   └─ commons-x:1.0
 │
 └─ library-b
     └─ commons-x:2.0

이런 경우 Maven의 의존성 중재 규칙에 따라 실제 사용할 버전이 결정된다.

대표적으로 알려진 규칙이 nearest definition이다.

의존성 그래프에서
현재 프로젝트와 더 가까운 위치에 선언된 버전이 우선

하지만 dependencyManagement에서 특정 버전을 관리하면 전이 의존성의 버전 선택을 프로젝트 차원에서 통제할 수 있다.

예를 들어 BOM이 다음과 같이 관리한다고 하자.

commons-x → 2.5

그렇다면 여러 라이브러리가 서로 다른 commons-x 버전을 요구하더라도 프로젝트에서는 관리된 버전으로 통일할 수 있다.

그래서 BOM은 단순히 <version>을 안 적기 위한 편의 기능만이 아니다.

더 중요한 목적은

프로젝트 전체의 의존성 버전 정책을 일관되게 유지하는 것이다.


10. BOM이 특히 유용한 멀티모듈 프로젝트

멀티모듈 프로젝트에서는 BOM의 장점이 더 크게 나타난다.

예를 들어 다음과 같은 프로젝트가 있다고 하자.

my-service
├─ api
├─ domain
├─ batch
├─ infrastructure
└─ common

각 모듈이 제각각 버전을 관리하면 이런 상황이 생길 수 있다.

api
spring-web 6.x.1

batch
spring-core 6.x.3

infrastructure
jackson 2.x.4

common
jackson 2.x.1

모듈이 많아질수록 버전 관리가 복잡해진다.

공통 BOM을 사용하면 다음과 같이 관리할 수 있다.

                   BOM
                    │
          ┌─────────┼─────────┐
          │         │         │
         api      batch     domain
          │         │         │
          └─────────┼─────────┘
                    │
              동일한 버전 정책

각 모듈은 어떤 버전을 사용해야 하는지 직접 고민할 필요가 줄어든다.


11. 실제 적용된 버전 확인하기

Maven이 최종적으로 어떤 설정을 사용하고 있는지 확인하려면 effective-pom을 보는 것이 좋다.

Maven Wrapper를 사용한다면 다음 명령을 실행한다.

./mvnw help:effective-pom

Windows에서는 다음과 같이 실행할 수 있다.

.\mvnw.cmd help:effective-pom

여기에는

Parent POM
BOM
dependencyManagement
현재 pom.xml

등이 합쳐진 최종 Maven 설정이 출력된다.

예를 들어 프로젝트의 pom.xml에서는

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
</dependency>

처럼 버전이 없더라도 effective-pom에서는 실제 관리되고 있는 버전을 확인할 수 있다.


12. dependency:tree로 최종 의존성 확인하기

의존성 구조는 다음 명령으로 확인할 수 있다.

./mvnw dependency:tree

Windows라면:

.\mvnw.cmd dependency:tree

결과는 대략 다음과 같은 형태다.

com.example:demo
+- org.springframework.boot:spring-boot-starter-web
|  +- org.springframework:spring-web
|  +- org.springframework:spring-webmvc
|  +- com.fasterxml.jackson.core:jackson-databind
|  \- org.apache.tomcat.embed:tomcat-embed-core

여기서 확인할 것은 세 가지다.

어떤 라이브러리가 들어왔는가?
어떤 경로를 통해 들어왔는가?
최종적으로 어떤 버전이 선택됐는가?

특히 의존성 충돌을 분석할 때 dependency:tree는 상당히 자주 사용한다.


13. BOM 사용 흐름 정리

전체 흐름을 정리하면 다음과 같다.

BOM
│
│ 호환 가능한 라이브러리 버전 정의
│
▼
dependencyManagement
│
│ BOM import
│
▼
프로젝트의 버전 정책 생성
│
▼
dependencies
│
│ 실제 사용할 라이브러리 선언
│
▼
버전을 생략해도 관리된 버전 적용

여기서 가장 중요한 것은 역할을 구분하는 것이다.

BOM
= 여러 라이브러리의 버전 정책을 담은 POM

dependencyManagement
= 해당 버전 정책을 관리하는 Maven 기능

dependencies
= 실제 프로젝트에 사용할 라이브러리 선언

14. 직접 확인해보기

Spring Boot Maven 프로젝트가 있다면 다음 순서로 확인해볼 수 있다.

먼저 pom.xml에서 버전이 없는 의존성을 하나 찾는다.

예를 들면:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

그다음:

.\mvnw.cmd help:effective-pom

을 실행해 Maven이 실제로 사용하는 버전 관리 설정을 확인한다.

그리고:

.\mvnw.cmd dependency:tree

를 실행해서 실제 의존성 트리와 최종 버전을 확인한다.

좀 더 특정 라이브러리만 보고 싶다면 다음처럼 필터링할 수도 있다.

.\mvnw.cmd dependency:tree -Dincludes=org.springframework

Jackson만 확인하고 싶다면:

.\mvnw.cmd dependency:tree -Dincludes=com.fasterxml.jackson.core

단순히 문서를 읽는 것보다 이 명령들을 직접 실행해보면 BOM이 어떤 역할을 하는지 훨씬 명확하게 보인다.


마무리

Maven BOM은 단순히 pom.xml에서 <version>을 생략하기 위한 기능이 아니다.

핵심은 여러 라이브러리의 버전을 하나의 정책으로 관리하고, 프로젝트 전체에서 호환되는 의존성 조합을 유지하는 것이다.

특히 Spring Boot처럼 수십 개 이상의 오픈소스 라이브러리가 함께 움직이는 프로젝트에서는 개발자가 모든 라이브러리의 호환성을 직접 관리하는 것이 현실적으로 어렵다.

Spring Boot BOM이 그 복잡도를 대신 관리해주는 것이다.

마지막으로 세 가지만 구분하면 된다.

dependencyManagement
→ 의존성의 버전과 정책을 관리한다.

BOM
→ 여러 의존성의 버전 정보를 하나의 POM으로 제공한다.

dependencies
→ 실제 프로젝트에서 사용할 의존성을 추가한다.

그리고 Maven 프로젝트에서 버전이 어디서 결정됐는지 헷갈릴 때는

./mvnw help:effective-pom
./mvnw dependency:tree

두 명령부터 확인하면 된다.

profile
레거시를 이해하면서도 새로운 기술을 현실적으로 적용할 수 있는 백엔드 개발자가 되는 것이 목표입니다.

0개의 댓글