🔖Contents
질문의 배경
최근 Spring Boot 프로젝트를 생성할 때 99%는 Gradle-Groovy 를 선택합니다. build.gradle 파일을 통해 필요한 라이브러리를 간편하게 관리하고, Github Action 혹은 AWS 인스턴스에서 배포할 때 gradlew 명령으로 쉽게 배포할 수 있었습니다. 하지만 실무와 근접한 프로젝트를 구성하거나 commons 처럼 라이브러리 모듈을 구현하기 위해서는 라이브러리 버전 관리가 필요합니다. 자연스럽게 Gradle을 좀 더 자세히 알아야 할 필요성 느끼게 되면서 정리하게 된 글 입니다.
1. Gradle
Gradle은 자바, C++, 안드로이드 등 다양한 프로그래밍 언어로 작성된 소프트웨어의 빌드 및 관리를 자동화하는 오픈소스 도구입니다.(빌드 자동화 도구)
프로젝트의 의존성 관리, 컴파일, 테스트, 배포 등의 복잡한 작업을 자동화하여 개발 효율성을 높입니다.

기능 및 특징
- 빌드 자동화: 소스 코드를 컴파일하고 실행 가능한 애플리케이션으로 만드는 과정을 자동화합니다.
- 의존성 관리: 프로젝트에 필요한 외부 라이브러리나 의존성을 자동으로 관리하고 다운로드합니다.
- 다국어 지원: 자바, 스칼라, C++, 파이썬 등 다양한 언어를 지원합니다.
- 유연성과 확장성: Groovy를 기반으로 하여 유연하고 확장 가능한 빌드 스크립트를 작성할 수 있습니다.
- 플러그인 시스템: 다양한 기능을 플러그인 형태로 제공하며, 이를 통해 빌드 로직을 재사용하고 확장할 수 있습니다.
- 안드로이드 공식 빌드 도구: 특히 안드로이드 애플리케이션 개발에서 공식 빌드 시스템으로 널리 사용됩니다.
- 이전 빌드 도구의 단점 보완: Ant의 자유도와 Maven의 관례를 결합하여 이전 빌드 도구들의 단점을 보완했습니다.
2. 스프링 부트에서 Gradle
스프링 부트는 Gradle을 통해 소스 코드를 컴파일하고, 의존성(라이브러리)를 관리하며, 애플리케이션을 실행 가능한 파일로 생성하는 등 개발 프로젝트의 전반적인 빌드 과정을 자동화하는 역할을 수행합니다. Gradle은 XML 기반의 Maven과 달리 Groovy 기반으로 build.gradle 파일에 스크립트를 작성하여 유연하고 간편한 설정을 관리할 수 있습니다.
스프링 부트에서 Gradle의 핵심 역할
- 빌드 자동화: 소스 코드 컴파일, 테스트 실행, 패키징 등 빌드 프로세스를 자동화합니다.
- 의존성 관리: 프로젝트에 필요한 외부 라이브러리(의존성)를 설정하고 자동으로 다운로드 및 관리합니다.
- 간편한 설정:
build.gradle파일에 스크립트를 작성하므로, 복잡한 XML보다 관리가 용이하고 유연합니다.
2.1 gradlew, gradlew.bat
gradlew와 gradlew.bat은 스프링 부트 프로젝트에서 Gradle Wrapper의 실행 스크립트입니다. gradlew는 리눅스/macOS 용 셸 스크립트이고, gradlew.bat는 윈도우용 배치 스크립트입니다. 이 스크립트들을 통해 프로젝트를 빌드, 실행, 테스트하는 등의 Gradle 작업을 수행할 수 있으며, 이 방법은 특정 Gradle 버전에 의존할 필요 없이 일관된 빌드 환경을 제공합니다.
gradlew와 gradlew.bat의 역할
- 프로젝트 빌드 및 실행:
gradlew또는gradlew.bat에bootRun,build와 같은 명령어를 붙여 애플리케이션을 실행하거나 빌드할 수 있습니다. - 운영체제별 스크립트: 각 파일은 특정 운영체제에 맞게 작성되었습니다.
gradlew: Linux, macOS 등에서 사용됩니다. (Linux/macOS:./gradlew bootRun)gradlew.bat: Windows에서 사용됩니다. (Windows:gradlew.bat bootRun)
- Gradle Wrapper 사용: 프로젝트를 실행할 때 로컬에 Gradle이 설치되어 있지 않아도, 이 스크립트들이 자동으로 해당 Gradle 버전을 다운로드하고 사용하도록 합니다. 이처럼 Gradle Wrapper를 사용하면 개발 환경에 관계없이 동일한 버전의 Gradle을 사용할 수 있습니다.
2.2 Gradle Wrapper
Gradle Wrapper는 Gradle 빌드 도구를 사용하기 위해 프로젝트에 포함된 도구로, 사용자가 Gradle을 로컬 환경에 직접 설치하지 않아도 빌드 작업을 실행할 수 있게 해줍니다. gradle-wrapper.jar 파일과 설정(gradle/wrapper/gradle-wrapper.properties)을 포함하며, 프로젝트를 빌드하는 데 필요한 특정 버전의 Gradle을 자동으로 다운로드하여 실행해줍니다.
Gradle Wrapper의 역할 및 장점
- 환경 종속성 제거: 개발자마다 Gradle을 설치하고 환경 변수를 설정하는 번거로움을 없애줍니다.
- 일관된 빌드 환경: 프로젝트의 빌드 스크립트가 특정 버전의 Gradle을 실행하도록 강제하여 모든 개발 환경에서 동일한 방식으로 빌드되도록 보장합니다.
- 자동화된 다운로드 및 실행: 사용자가 프로젝트를 처음 로드하거나
gradlew명령어를 실행하면,gradle-wrapper.jar가gradle-wrapper.properties파일에 명시된 버전의 Gradle을 자동으로 다운로드하고 캐싱하여 사용합니다. - 간편한 실행: 프로젝트 루트 디렉토리에서
./gradlew또는gradlew.bat와 같은 명령어를 사용하여 빌드, 테스트, 실행 등의 작업을 수행할 수 있습니다.
Gradle Wrapper 작동 방식
- 빌드 명령어 실행: 개발자가 프로젝트 디렉토리에서
./gradlew와 같은 명령어를 입력합니다. - Wrapper 실행: 시스템은
gradle-wrapper.jar파일을 실행합니다. - Gradle 버전 확인:
gradle-wrapper.properties파일에서 필요한 Gradle 버전을 확인합니다. - Gradle 다운로드: 로컬 환경에 해당 버전의 Gradle이 설치되어 있지 않으면, Wrapper가 자동으로 Gradle을 다운로드하여 로컬 캐시에 저장합니다.
- Gradle 작업 실행:다운로드된 Gradle을 사용하여 사용자가 요청한 빌드 작업을 수행합니다.
gradle-wrapper.properties 파일 수정으로 Gradle 빌드 작업을 환경에 따라서 설정할 수 있습니다. 현재 내용에서는 생략하고 넘어가겠습니다.
2.3 build.gradle
build.gradle은 Gradle이라는 빌드 도구의 설정 파일로, 스프링 부트 프로젝트의 빌드 방식, 의존성(라이브러리), 환경 설정 등을 정의하는 데 사용됩니다. build.gradle 파일을 통해 프로젝트의 빌드 및 관리를 자동화하고, 필요한 라이브러리를 설정하며, 프로젝트의 전반적인 구성을 기술할 수 있습니다.
build.gradle 기능
- 빌드 자동화: 소스 코드를 컴파일하고 테스트하며, 실행 가능한 애플리케이션으로 패키징하는 빌드 과정을 자동화합니다.
- 의존성 관리: 프로젝트에 필요한 라이브러리나 프레임워크(예: Spring Boot Starter)를 추가하고 관리합니다.
- 환경 설정: 프로젝트 빌드 시 필요한 다양한 환경 설정을 정의할 수 있습니다.
- 스크립트 사용:빌드 스크립트는 Groovy나 Kotlin과 같은 도메인 특화 언어(DSL)로 작성되어 있어, 빌드 로직을 유연하게 제어할 수 있습니다.
build.gradle 파일 해심 구성
buildscript: 빌드에 필요한 플러그인 등 외부 라이브러리를 가져올 때 사용합니다.repositories: 의존성을 다운로드할 저장소(Repository)를 지정합니다.dependencies: 프로젝트가 의존하는 라이브러리들을 나열합니다.plugins: 사용 중인 Gradle 플러그인을 명시합니다.
2.4 settings.gradle
settings.gradle은 스프링 부트 프로젝트에서 빌드에 포함될 모듈을 정의하고 프로젝트 구조를 설정하는 파일입니다. 이 파일은 루트 프로젝트 디렉터리에 위치하며, 여러 모듈로 구성된 멀티 프로젝트의 경우에는 어디서 플러그인을 가져올지, 라이브러리 저장소는 어디인지, 모듈은 어떤 것들이 있는지를 한 곳에서 관리합니다.
- 프로젝트 설정: 프로젝트를 구성하고, 특히 멀티 프로젝트의 경우 포함해야 할 서브 프로젝트들을 정의합니다.
- 빌드 구성: 프로젝트를 빌드할 때 어떤 모듈들을 포함해야 하는지 Gradle에게 알려주어 빌드 과정을 제어합니다.
- 싱글 프로젝트와 멀티 프로젝트: 싱글 프로젝트에서는 특별히 설정할 내용이 적지만, 여러 서브 프로젝트로 구성된 복잡한 프로젝트에서는 각 프로젝트의 이름을 지정하는 데 사용됩니다.
3. Gradle.build 분석
멀티 모듈 프로젝트에서 루트 디렉토리의 build.gradle 입니다. 각 코드 줄마다 무엇을 의미하는지 분석하겠습니다.
plugins {
id 'java'
alias(libs.plugins.spring.boot) apply false
alias(libs.plugins.depman) apply false
}
// 모듈 역할 명시
def appModuleNames = ['commerce-catalog', 'commerce-order', 'commerce-payment', 'commerce-user']
def libModuleNames = ['commerce-commons']
allprojects {
group = 'com.app'
version = '0.0.1-SNAPSHOT'
description = 'commerce'
// repositories는 settings.gradle에서만 관리
}
subprojects {
apply plugin: 'java'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(libs.versions.java.get() as String)
}
}
// 전역 규약: 테스트 & 애너테이션 프로세서만
dependencies {
testImplementation platform(libs.junit.bom)
testImplementation libs.junit.jupiter
compileOnly libs.lombok
annotationProcessor libs.lombok
}
// lombok annotationProcessor를 compileOnly에 자동 포함(편의)
configurations {
compileOnly { extendsFrom annotationProcessor }
}
tasks.named('test') {
useJUnitPlatform()
}
// === 애플리케이션 모듈 전용 규약 ===
if (name in appModuleNames) {
apply plugin: libs.plugins.spring.boot.get().pluginId
apply plugin: libs.plugins.depman.get().pluginId
// 공통 실행 스타터(앱만)
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-validation'
}
// Boot 기본: bootJar=true, jar=false — 기본값 유지
}
// === 라이브러리 모듈 전용 규약 ===
if (name in libModuleNames) {
apply plugin: 'java-library'
// 라이브러리는 JAR만 생성 (bootJar 적용 안 함)
}
}
3.1 plugins {}
plugins {} 블록은 “이 빌드에서 어떤 플러그인(기능)을 쓸지” 선언합니다.
plugins {
id 'java'
alias(libs.plugins.spring.boot) apply false
alias(libs.plugins.dependency-management) apply false
}
id 'java': 자바 프로젝트로 빌드를 의미(컴파일, 테스트, JAR 만들기 등 기본 작업을 수행합니다.)alias(libs.plugins.spring.boot): 버전 카탈로그(libs.versions.toml)에서 정의한 스프링부트 플러그인을 별칭(alias) 으로 조회apply false: "지금은 적용하지 않고 선언만 한 상태"를 의미depman:io.spring.dependency-management플러그인의 별칭. 스프링 생태계 BOM(버전 묶음)을 적용하도록 도움을 지원
💡BOM이란
스프링 부트 BOM(Bill of Materials)은 스프링 부트 프로젝트에서 사용되는 모든 의존성 라이브러리의 버전을 표준화하여 관리하는 '자재 명세서'입니다. 이를 통해 개발자는 각 라이브러리의 버전을 개별적으로 관리할 필요 없이, 스프링 부트가 권장하는 호환되는 버전으로 한 번에 설정하여 버전 충돌 없이 프로젝트를 구성할 수 있습니다.
- 버전 관리 자동화: 프로젝트에 필요한 모든 라이브러리의 버전을 정의해 두어, 개발자가 개별적으로 버전을 찾고 지정하는 번거로움을 없애줍니다.
- 의존성 충돌 방지: 스프링 부트 버전별로 최적화된 호환 가능한 라이브러리 버전이 묶여있기 때문에, 라이브러리 간의 버전 충돌을 방지합니다.
- 일관된 개발 환경: 모든 개발자가 동일한 BOM을 사용함으로써, 프로젝트의 빌드와 실행 환경을 일관되게 유지할 수 있습니다.
💡io.spring.dependency-management 플러그인이란
io.spring.dependency-management 플러그인은 스프링 부트의 spring-boot-dependencies BOM(Bill of Materials)을 자동으로 가져와 의존성 버전을 일관되게 관리해 주는 Gradle 플러그인입니다. 스프링 부트 프로젝트의 빌드 과정에서 이 플러그인을 적용하면, 프로젝트에서 사용 중인 스프링 부트 버전에 맞는 spring-boot-dependencies BOM을 자동으로 가져옵니다.
3.2 def
def는 그루비에서 변수 선언입니다. 나중에 등장하는 if (name in appModuleNames) 처럼, 현재 처리 중인 모듈 이름이 어디에 속하는지로 규칙을 나눕니다.
def appModuleNames = ['commerce-catalog', 'commerce-order', 'commerce-payment', 'commerce-user']
def libModuleNames = ['commerce-commons']
3.3 allprojects {}
allprojects {}는 루트와 모든 서브프로젝트에 공통 속성을 지정합니다. 안의 group, version, description은 식별자/메타정보입니다.
산출물 좌표가 com.app:모듈명:0.0.1-SNAPSHOT처럼 됩니다.
allprojects {
group = 'com.app'
version = '0.0.1-SNAPSHOT'
description = 'commerce'
// repositories는 settings.gradle에서만 관리
}
💡라이브러리 버전 관리하는 경우
현재 제 코드는 버전 카탈로그(libs.versions.toml)에서 직접 정의한 버전을 사용하도록 관리하고 있습니다.
만약, BOM을 통해 의존성 버전을 자동으로 관리하는 경우에는 아래 코드처럼 apply plugin:으로 플러그인을 작성하면 됩니다.
// 모든 프로젝트에 적용되는 설정
allprojects {
group = 'com.example'
version = '1.0.0-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
repositories {
mavenCentral()
}
}
3.4 subprojects {}
subproject {} 모든 서브 프로젝트의 공통적으로 적용되는 블록입니다.
1. apply plugin: 'java'
모든 서브프로젝트에 Java 플러그인을 적용합니다. (자바 소스 컴파일, 테스트 작업 생성)
subprojects {
apply plugin: 'java'
}
2. toolchain {}
어떤 JDK 버전으로 빌드할지 정합니다. libs.versions.java는 버전 카탈로그에서 java = "21" 같은 값을 읽어옵니다.
팀원이 다른 JDK를 깔아놔도, Gradle이 알아서 JDK 21을 내려 쓰게 할 수 있어, 환경 차이를 줄입니다.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(libs.versions.java.get() as String)
}
}
3. dependencies {}
dependencies {}는 의존성(라이브러리) 선언 블록입니다.
// 전역 규약: 테스트 & 애너테이션 프로세서만
dependencies {
testImplementation platform(libs.junit.bom)
testImplementation libs.junit.jupiter
compileOnly libs.lombok
annotationProcessor libs.lombok
}
testImplementation platform(libs.junit.bom): JUnit 의존성들의 버전을 BOM으로 한 번에 잠그는 문법platform(...)은 “이 묶음의 버전 정리표를 적용해”라는 의미testImplementation libs.junit.jupiter로 실제 JUnit Jupiter API를 조회. (버전 표기는 BOM이 담당)compileOnly libs.lombok은 컴파일할 때만 lombok을 쓰겠다는 뜻(런타임엔 필요 없음)annotationProcessor libs.lombok은 애너테이션 프로세서 경로에 lombok을 설정. (롬복이 소스에서 코드를 생성)
3.5 💡implementation, testImplementation, compileOnly, annotationProcessor
핵심 개념이자 선수 지식으로 의존성(Dependency)과 스코프(Scope)에 대해 먼저 이해해야만 합니다.

우선 의존성(Dependency)은 내 프로젝트(요리)를 만드는 데 필요한 외부 라이브러리(미리 만들어진 재료나 도구)라고 생각하시면 됩니다.
예를 들어, JSON 데이터를 다루기 위한 Gson 라이브러리나 테스트를 위한 JUnit 라이브러리가 핵심 재료 혹은 도구에요.
스코프(Scope)는 이 '재료'나 '도구'가 언제, 어디서 필요한지를 정해주는 규칙입니다. 모든 도구를 항상 부엌에 꺼내놓을 필요는 없잖아요? 요리할 때만 필요한 도구, 손님에게 나갈 때 필요한 도구, 맛을 테스트할 때만 필요한 도구가 각각 다른 것처럼요. implementation, testImplementation 등이 바로 이 스코프를 지정하는 키워드입니다.
정리 표
| 설정 | 역할 | 언제 필요? | 최종 결과물(앱)에 포함? | 대표적인 예시 |
|---|---|---|---|---|
implementation |
내부 구현에 필요한 라이브러리 | 컴파일 시점 & 런타임 시점 | O (포함됨) | Gson, Retrofit |
testImplementation |
테스트 코드 작성 및 실행에 필요한 라이브러리 |
테스트 컴파일 시점 & 테스트 런타임 시점 |
X (포함 안 됨) | JUnit, Mockito |
compileOnly |
컴파일 시점에만 필요한 라이브러리 | 오직 컴파일 시점 | X (포함 안 됨) | Lombok (어노테이션) |
annotationProcessor |
어노테이션을 처리하여 코드를 생성하는 도구 | 오직 컴파일 시점 (빌드 과정) | X (포함 안 됨) | Lombok (처리기) |
- 라이브러리 의존성: 프로젝트 실행에 필요한 외부 라이브러리(스프링 웹 스타터, 톰캣 등)를 의미하며,
스프링 부트 스타터를 통해 관리합니다.
4. configurations {}
configurations {}는 의존성 구역(스코프) 를 다루는 블록입니다.
compileOnly가 annotationProcessor를 상속하게 해서, IDE에서 롬복 인식을 더 자연스럽게 합니다. (편의 세팅)
// lombok annotationProcessor를 compileOnly에 자동 포함(편의)
configurations {
compileOnly { extendsFrom annotationProcessor }
}
5. tasks
tasks는 빌드 작업을 뜻합니다. test` 작업에 “JUnit Platform으로 실행해”라고 지정합니다. JUnit 5를 쓰려면 보통 이 설정이 필요합니다.
tasks.named('test') {
useJUnitPlatform()
}
7. 애플리케이션 모듈 전용 규약: 부트 & 의존성은 앱에만
현재 모듈 이름이 앱 목록에 있으면, 그때만 스프링부트 플러그인과 의존성 관리 플러그인을 적용합니다.
아까 plugins { ... apply false }로 “선언만” 해둔 걸, 여기서 실제로 적용하는 구조입니다.
이렇게 하면 라이브러리 모듈(예: commons)에는 스프링부트가 적용되지 않습니다.
if (name in appModuleNames) {
apply plugin: libs.plugins.spring.boot.get().pluginId
apply plugin: libs.plugins.depman.get().pluginId
8. 앱 모듈만 공통 주입
- 앱 모듈에만 부트 스타터를 공통 주입합니다.
- 버전을 적지 않는 이유는, 부트 BOM이 알아서 맞춰주기 때문입니다. (depman 플러그인이 그걸 가능하게 해 줌)
actuator는 헬스체크/메트릭,validation은jakarta.validation(Bean 검증) 스타터입니다.
// 공통 실행 스타터(앱만)
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-validation'
}
9. JAR(bootJar)
스프링부트 플러그인이 적용되면, 기본적으로 실행 가능한 JAR(bootJar) 를 만들고, 일반 JAR는 비활성화됩니다.
앱 모듈은 이 기본값을 그대로 쓰면 됩니다.
// Boot 기본: bootJar=true, jar=false — 기본값 유지
}
💡bootJar와 JAR란?
bootJar는 Spring Boot 프로젝트를 위한 실행 가능한 JAR 파일(내장 서버 포함 JAR)을 생성하는 Gradle 작업입니다. 이는 개발된 애플리케이션을 다른 환경에 배포하여 실행할 때 사용하며, 별도의 웹 서버 설치 없이 애플리케이션을 바로 실행할 수 있도록 해줍니다.
jar 작업과의 차이점
jar작업: 일반적인 Java 프로젝트의 경우,jar작업을 통해 클래스 파일만 포함된 JAR 파일을 생성합니다. 이 경우 애플리케이션을 실행하려면 별도의 JRE나 웹 서버를 설치하고 설정해야 합니다.bootJar작업:bootJar는jar작업의 결과물에 내장 서버와 같은 실행에 필요한 모든 것을 추가하여, 실행 가능한 독립적인 파일로 만들어줍니다.
10. 라이브러리 모듈 전용 규약: 순수 JAR, 부트 금지
- 라이브러리 모듈(commons 등)은
java-library플러그인을 적용합니다. java-library는api/implementation의존성 구분을 지원해서, 재사용 모듈 작성에 유리합니다.- 스프링부트 플러그인은 적용하지 않습니다. 따라서 bootJar가 아닌 일반 JAR가 생성됩니다.
- 여기엔 웹/JPA 같은 실행용 스타터를 넣지 않는 것이 원칙입니다. (의존 그래프 오염 방지)
if (name in libModuleNames) {
apply plugin: 'java-library'
// 라이브러리는 JAR만 생성 (bootJar 적용 안 함)
}
}
build.gradle 정리
- 자바/JDK 버전, 테스트(JUnit), 롬복 같은 공통 규약은 모든 모듈에 적용
- 스프링부트 + 실행 스타터는 앱 모듈에만 적용(실행 가능한 bootJar 생성)
- 라이브러리 모듈은
java-library만 적용(순수 JAR, 재사용성↑) - 버전과 저장소는 각각 버전 카탈로그(TOML) / settings.gradle 에서 중앙관리
4. settings.gradle 분석
루트 디렉토리의 settings.gradle 입니다. 각 코드 줄마다 무엇을 의미하는지 분석하겠습니다.
import org.gradle.api.initialization.resolve.RepositoriesMode
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}
dependencyResolutionManagement {
// 저장소는 settings에서만 관리
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
}
}
// ❗ versionCatalogs 블록 '전부' 제거 (자동 로드 사용)
rootProject.name = 'commerce'
include(
':commerce-commons',
':commerce-catalog',
':commerce-order',
':commerce-payment',
':commerce-user'
)
4.1 RepositoriesMode
RepositoriesMode라는 열거형(Enum)을 쓰기 위한 임포트입니다.
Groovy DSL에서는 이런 타입을 직접 참조할 때 가끔 임포트가 필요합니다. 뒤에서 저장소 정책을 정할 때 사용합니다.
import org.gradle.api.initialization.resolve.RepositoriesMode
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}
플러그인을 어디서 내려받을지 정합니다. 플러그인은 “빌드에 기능을 끼우는 애드온”이고, 일반 라이브러리와 저장소 설정을 분리합니다. gradlePluginPortal()은 공식 플러그인 저장소, mavenCentral()은 우리가 익숙한 중앙 저장소입니다. 이렇게 두면 plugins { id "org.springframework.boot" version "…" } 같은 선언이 문제없이 동작합니다.
4.2 dependencyResolutionManagement {}
dependencyResolutionManagement 블록은 라이브러리 의존성을 어디서, 어떤 정책으로 받을지 전역으로 정하는 블록입니다.
repositoriesMode가 핵심입니다. FAIL_ON_PROJECT_REPOS는 “각 모듈의 build.gradle 어디에서도 repositories { … }를 쓰지 마라. 쓰면 빌드 실패로 처리하겠다”는 뜻입니다. 즉, 저장소 선언을 settings.gradle 한 곳에만 두어 팀 전체 설정을 일관되게 유지합니다.
저장소는 mavenCentral() 하나로 고정했습니다. 사내 Nexus/Artifactory가 있다면 여기에 추가하면 됩니다. 참고로 다른 모드로는 PREFER_SETTINGS(프로젝트에서도 선언은 허용하되 settings 우선), PREFER_PROJECT(프로젝트 우선) 등이 있지만, 멀티 모듈·팀 개발에서는 지금처럼 중앙집중이 유지보수에 가장 유리합니다.
dependencyResolutionManagement {
// 저장소는 settings에서만 관리
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
}
}
4.3 versionCatalogs {} 제거
// ❗ versionCatalogs 블록 '전부' 제거 (자동 로드 사용)
주석이지만 매우 중요합니다. Gradle은 gradle/libs.versions.toml 파일이 존재하면 자동으로 libs 카탈로그를 로드합니다. 따라서 versionCatalogs { create("libs") { from(files("gradle/libs.versions.toml")) } }를 또 쓰면 “한 카탈로그에서 from은 한 번만 호출 가능” 오류가 납니다. 자동 로드와 수동 선언을 둘 중 하나만 쓰는 게 원칙이고, 우리는 자동 로드를 선택했기 때문에 이 블록을 아예 적지 않습니다.
4.4 rootProject.name
루트 프로젝트의 이름입니다. IDE에서 보이는 프로젝트 표시, 기본 산출물 좌표, 보고서 제목 등 여러 곳에 쓰입니다.
팀에서 합의한 읽기 좋은 이름으로 두면 됩니다.
rootProject.name = 'commerce'
4.5 include
여기서 멀티 모듈을 선언합니다. 각 문자열은 루트 디렉터리 하위의 모듈 폴더를 가리킵니다. 예를 들어 :commerce-commons는 commerce-commons/ 디렉터리의 서브프로젝트를 의미하고, 그 안의 build.gradle이 그 모듈의 규칙이 됩니다. 앞의 콜론(:)은 “루트 기준”을 뜻하는 네임스페이스 표시입니다.
새로운 모듈을 추가할 땐 폴더를 만들고 include(':새모듈')만 추가하면 Gradle이 그 모듈을 인식합니다.
include(
':commerce-commons',
':commerce-catalog',
':commerce-order',
':commerce-payment',
':commerce-user'
)
🔥settings.gradle 설정이 만들어 주는 개발 경험
settings.gradle 파일 하나로 플러그인 저장소와 라이브러리 저장소를 분리 관리하고, 저장소 선언을 전역 한 곳으로 모아서 팀 환경을 통일합니다. 버전 카탈로그는 자동 로드로 충돌을 원천 차단하고, 모듈 목록을 명시하여 IDE와 빌드가 동일한 구조를 바라보게 합니다. 결과적으로 각 모듈의 build.gradle은 비즈니스에 필요한 의존성만 깔끔히 적으면 되고, 버전과 저장소 같은 인프라 설정은 여기서 끝납니다.
🔥settings.gradle 설정에서 겪은 문제
가장 흔한 실수는 버전 카탈로그 중복 선언입니다. gradle/libs.versions.toml이 있는데 versionCatalogs { create("libs") { from(...) } }까지 쓰면 즉시 충돌합니다. 또 하나는 FAIL_ON_PROJECT_REPOS를 켰는데 서브프로젝트 어딘가에 repositories { ... }가 남아 있는 경우입니다. 이런 경우 빌드가 바로 실패하니, 저장소 선언은 반드시 settings에서만 관리하세요. 필요 시 정책을 임시로 PREFER_SETTINGS로 완화할 수 있지만, 장기적으로는 중앙집중이 안정적입니다.
5. 버전 카탈로그 toml
버전 카탈로그(Version Catalog)는 “의존성/플러그인 버전을 한 곳에서 이름으로 관리” 하는 Gradle 기능입니다. 파일 형식은 TOML(Tom’s Obvious, Minimal Language)이라는 키=값 기반 설정 포맷이고, Gradle은 기본 경로 gradle/libs.versions.toml을 자동으로 읽어 libs라는 카탈로그로 제공합니다. 그래서 build.gradle 안에서 libs.xxx 형태로 파일명을 사용합니다.
멀티 모듈 프로젝트에서 gradle/libs.versions.toml 입니다. 무엇을 의미하는지 분석하겠습니다.
[versions]
java = "21"
spring-boot = "3.5.6"
depman = "1.1.7"
lombok = "1.18.34"
mapstruct = "1.6.2"
jjwt = "0.12.6"
querydsl = "5.1.0"
redisson = "3.52.0"
junit = "5.11.4"
[plugins]
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }
depman = { id = "io.spring.dependency-management", version.ref = "depman" }
[libraries]
# 테스트 공통
junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter" }
redisson = { module = "org.redisson:redisson", version.ref = "redisson" }
# 애너테이션/매핑
lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" }
mapstruct = { module = "org.mapstruct:mapstruct", version.ref = "mapstruct" }
mapstruct-processor = { module = "org.mapstruct:mapstruct-processor", version.ref = "mapstruct" }
# 선택: 보안/쿼리
jjwt-api = { module = "io.jsonwebtoken:jjwt-api", version.ref = "jjwt" }
jjwt-impl = { module = "io.jsonwebtoken:jjwt-impl", version.ref = "jjwt" }
jjwt-jackson = { module = "io.jsonwebtoken:jjwt-jackson", version.ref = "jjwt" }
querydsl-jpa = { module = "com.querydsl:querydsl-jpa", version.ref = "querydsl" }
5.1 toml 문법
- 키=값:
java = "21" - 섹션:
[versions],[plugins],[libraries]처럼 대괄호로 그룹을 나눕니다. - 객체(맵):
{ id = "...", version.ref = "..." }이런 중괄호는 한 줄짜리 객체입니다. - 문자열은 큰따옴표
"...", 숫자는 따옴표 없이 씁니다.
5.2 [versions]
- 각 키는 버전 별칭입니다. 아래 섹션에서
version.ref = "lombok"처럼 참조합니다. - 장점: 버전 업그레이드는 여기만 바꾸면 전 프로젝트에 반영됩니다.
[versions]
java = "21"
spring-boot = "3.5.6"
depman = "1.1.7"
lombok = "1.18.34"
mapstruct = "1.6.2"
jjwt = "0.12.6"
querydsl = "5.1.0"
redisson = "3.52.0"
junit = "5.11.4"
5.3 [plugins]
id는 플러그인 ID,version.ref는 위[versions]에서 정의한 버전을 가리킵니다.
[plugins]
spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" }
depman = { id = "io.spring.dependency-management", version.ref = "depman" }
build.gradle에서 다음과 같이 사용할 수 있습니다.
// root: build.gradle
plugins {
alias(libs.plugins.spring.boot) apply false
alias(libs.plugins.depman) apply false
}
apply false: "지금은 적용하지 않고 선언만 한 상태"를 의미
5.4 [libraries]
module = "그룹:아티팩트"가 핵심. Maven 좌표를 이름 하나(libs.redisson)로 부를 수 있게 별칭을 만드는 것입니다.version.ref = "..."는[versions]에 적어둔 버전을 참조합니다.junit-jupiter는 버전이 비어있죠? 이유는 옆에서 BOM으로 버전을 잠궈서 개별 버전이 불필요하기 때문이에요.
[libraries]
# 테스트 공통
junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter" }
redisson = { module = "org.redisson:redisson", version.ref = "redisson" }
# 애너테이션/매핑
lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" }
mapstruct = { module = "org.mapstruct:mapstruct", version.ref = "mapstruct" }
mapstruct-processor = { module = "org.mapstruct:mapstruct-processor", version.ref = "mapstruct" }
# 선택: 보안/쿼리
jjwt-api = { module = "io.jsonwebtoken:jjwt-api", version.ref = "jjwt" }
jjwt-impl = { module = "io.jsonwebtoken:jjwt-impl", version.ref = "jjwt" }
jjwt-jackson = { module = "io.jsonwebtoken:jjwt-jackson", version.ref = "jjwt" }
querydsl-jpa = { module = "com.querydsl:querydsl-jpa", version.ref = "querydsl" }
💡build.gradle에서 라이브러리 의존성을 사용하는 방법
- 스프링 부트 스타터(예:
spring-boot-starter-web)는 버전 적지 마세요. - 부트 BOM이 자동으로 관리합니다. (
dependency-management플러그인 적용 시)
// product: build.gradle
plugins { id 'java' } // 부트 플러그인은 루트에서 주입됨
dependencies {
implementation project(':commerce-commons')
// 모듈별 필요한 스타터만 — 버전 기입 금지(BOM이 관리)
implementation 'org.springframework.boot:spring-boot-starter-web'
// 필요 시 추가
// implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// implementation 'org.springframework.boot:spring-boot-starter-security'
// 선택: JJWT
// implementation libs.jjwt.api
// runtimeOnly libs.jjwt.impl
// runtimeOnly libs.jjwt.jackson
// 선택: QueryDSL (JPA 쓸 때)
// implementation libs.querydsl.jpa
// annotationProcessor "jakarta.persistence:jakarta.persistence-api"
// annotationProcessor "jakarta.annotation:jakarta.annotation-api"
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
💡새로운 라이브러리 추가하는 방법
# 1. TOML에 버전과 라이브러리 별칭 추가
[versions]
guava = "33.2.1-jre"
[libraries]
guava = { module = "com.google.guava:guava", version.ref = "guava" }
// 모듈의 build.gradle에서 사용
dependencies {
implementation libs.guava
}
“한 번 더 추가해야 하나요?” → 아니요. TOML + build.gradle 딱 두 군데면 끝.
// Redisson 쓰기
dependencies {
implementation libs.redisson
}
// JJWT 쓰기
dependencies {
implementation libs.jjwt.api
runtimeOnly libs.jjwt.impl
runtimeOnly libs.jjwt.jackson
}
# QueryDSL (JPA)
dependencies {
implementation libs.querydsl.jpa
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
}
5.5💡 java-library는 java 플러그인의 상위 버전
java-library 플러그인은 “라이브러리 모듈”을 만들 때 쓰라고 나온 플러그인이고, API/구현 의존성 구분을 지원합니다.
예를 들어, 아래 코드처럼 commons 공통 모듈의 build.gradle 안에서 java-library 플러그인을 사용을 권장합니다.
- java 플러그인: 자바 컴파일, 테스트, 패키징 지원(implementation, compileOnly, runtimeOnly)
- java-library 플러그인: 위 기능 + api 구성이 추가 → 소비자에게 전이되는 컴파일 의존성을 선언할 수 있습니다.
# commons: build.gradle
plugins {
id 'java-library'
alias(libs.plugins.depman)
}
dependencyManagement {
imports {
mavenBom "org.springframework.boot:spring-boot-dependencies:${libs.versions.spring.boot.get()}"
}
}
dependencies {
api(libs.jakarta.persistence) // JPA 애노테이션
api(libs.spring.data.commons) // Auditing 메타 애노테이션
api(libs.spring.data.jpa) // AuditingEntityListener
compileOnly(libs.hibernate.core) // 선택: 하이버네이트 애노테이션이 필요할 때만
compileOnly(libs.lombok) // Lombok
annotationProcessor(libs.lombok)
}
반드시 위의 코드처럼 작성해야만 라이브러리 모듈의 의존성을 최소화하는 것은 아니에요.
Gradle 문법과 Spring 생태계를 관리하는 방법을 충분히 알고 있다면 언제든지 원하는 방법으로 코드를 수정하실 수 있습니다.
| 항목 | java | java-library |
| 기본 기능(컴파일, 테스트, JAR) | ✅ | ✅ |
| api 구성(공개 API 전이) | ❌ | ✅ |
| implementation(구현 전용) | ✅ | ✅ |
| 라이브러리 제작에 최적화 | 보통 | 권장 |
💡 commons-domain에 java-library가 좋은 이유
commons-domain은 다른 모듈이 가져다 쓰는 라이브러리 역할이니까, 외부에 노출해야 하는 최소 의존성(예: jakarta.persistence-api)은 api 로, 내부 구현에만 쓰는 것(예: spring-data-jpa, spring-data-commons)은 implementation 으로 숨길 수 있어요. → 소비자 모듈의 클래스패스가 불필요하게 더러워지지 않습니다.
api 뭘까?
api는 java-library 플러그인이 추가해 주는 의존성 구분자입니다.
api의 핵심 용도는 “이 모듈을 사용하는 쪽의 컴파일 클래스패스에도 같이 보낸다.”→ 즉, 전이(Transitive) 컴파일 의존성이에요.
- implementation: “이 모듈 안에서만 사용한다."→ 전이가 안 됩니다.(소비자에겐 런타임까지만 보장될 수 있습니다).
- compileOnly: 컴파일에만 필요, 런타임엔 없음(롬복, 애노테이션 등)
- runtimeOnly: 런타임에만 필요(DB 드라이버 등)
- annotationProcessor: 애노테이션 프로세서 전용(롬복, MapStruct 등)
예를 들어, 너의 라이브러리 public 메서드/필드/부모클래스/인터페이스에 외부 타입이 노출되면, 그 타입의 라이브러리를 소비자 컴파일러도 알아야 하니까 api를 사용하는 것이에요. 예를 들어 commons 모듈 안의 BaseEntity를 catalog 모듈 안의 엔티티에서 사용하는 것 입니다.
어떤 의존성이 어떤 범위로 선언했는지 궁금하다면 아래 명령어로 결과를 볼 수 있습니다. 아래 코드는 commerce-catalog가 commons를 implementation project(":commerce-commons")로 의존한다고 가정하고, 클래스패스에 commerce-commons가 올라왔는지 확인하기 위한 명령이에요.
# 전체 클래스패스 조회
./gradlew :commerce-catalog:dependencies --configuration compileClasspath

특정 라이브러리 의존성은 dependencyInsight 명령으로 확인을 할 수 있습니다. 유용하게 사용하는 방식에요.
# 예: jakarta.persistence-api가 어떻게 들어왔는지
./gradlew :commerce-catalog:dependencyInsight \
--configuration compileClasspath \
--dependency jakarta.persistence-api
# 예: spring-data-jpa가 전이되었는지
./gradlew :commerce-catalog:dependencyInsight \
--configuration compileClasspath \
--dependency spring-data-jpa

첨부된 그림을 보면 jakarta.persistence-api:3.1.0가 두 경로로 들어와요.
1. hibernate-core → spring-boot-starter-data-jpa (소비자 모듈이 직접 가지는 경로)
2. project :commerce-commons (← commons에서 전이되어 들어온 경로)
트리에서 \--- project :commerce-commons가 보이면, 그 라이브러리는 commons의 api로 공개되어 소비자(여기서는 commerce-catalog)의 compileClasspath로 전이된 거예요. 하지만 jakarta.persistence-api:3.1.0 compileClasspath 가 두 경로가 존재합니다.
아래 코드처럼 commons를 api ➜ implementation 수정하면 catalog로 전이 의존성을 하지 않습니다.
수정된 내용을 반영하기 위해서는 다시 Gradle을 build 합니다.
plugins {
id 'java-library'
alias(libs.plugins.depman)
}
dependencyManagement {
imports {
mavenBom "org.springframework.boot:spring-boot-dependencies:${libs.versions.spring.boot.get()}"
}
}
dependencies {
implementation(libs.jakarta.persistence) // ← api ➜ implementation
implementation(libs.spring.data.commons) // ← api ➜ implementation
implementation(libs.spring.data.jpa) // ← api ➜ implementation
compileOnly(libs.hibernate.core)
compileOnly(libs.lombok)
annotationProcessor(libs.lombok)
}

jakarta.persistence-api가 오직 hibernate-core → spring-boot-starter-data-jpa 경로로만 들어오고, project :commerce-commons 경로가 사라졌습니다. → commons에서 전이 끊긴 상태입니다.
catalog의 compileClasspath에 jakarta.persistence-api가 starter 경로 하나만 존재하는지 확인하면 됩니다.
api?, implementation? 언제 무엇을 사용하는지 감으로 외우시면 됩니다.
- 재사용 라이브러리/모듈: java-library
- api: 공개 API에 드러나는 타입의 의존성 (진짜 최소만)
- implementation: 내부에서만 쓰는 것들
- 실행 앱 모듈(Spring Boot 앱 등): 보통 org.springframework.boot(내부적으로 java)만 있으면 충분
애플리케이션이면 java(또는 Spring Boot 플러그인), 공용 라이브러리면 java-library을 사용하고, java-library를 쓰면 api/implementation로 경계를 깨끗하게 나눌 수 있습니다.
💡BOM(플랫폼)과 카탈로그의 역할 분담

스프링/부트 생태계(starter-web, starter-data-jpa 등)는 부트 BOM이 버전을 통째로 관리합니다. 그래서 implementation 'org.springframework.boot:…'에서 버전을 명시하지 않습니다. 단, 외부 라이브러리(Redisson, JJWT, MapStruct, QueryDSL 등)는 TOML의 [versions]에서 버전을 고정하고 [libraries]로 alias를 만들어 사용합니다.
'🍃SpringBoot' 카테고리의 다른 글
| 1. Spring Data JPA 이론: SQL 중심적인 개발의 문제점과 JPA 등장 (0) | 2026.01.10 |
|---|---|
| Spring Boot: Spring Data JPA With Auditing (0) | 2025.10.23 |
| Spring Boot: 멀티 모듈 (0) | 2025.10.22 |
| Spring Boot: Docker Compose와 application.yml 설정 기준 (0) | 2025.10.21 |