Java 17과 Maven을 사용해 core, spring-boot, demo 형태의 멀티 모듈 프로젝트를 구성하는 방법을 설명합니다. 공통 로직, Spring Boot 실행 모듈, 데모 모듈을 분리하는 실무형 구조와 pom.xml 예제를 정리합니다.

Java 17 Maven 멀티 모듈 프로젝트 구성 방법: core, spring-boot, demo 구조 예제
Java 17 Maven 멀티 모듈 프로젝트를 구성하면 하나의 프로젝트 안에서 공통 로직, Spring Boot 실행 애플리케이션, 테스트용 데모 애플리케이션을 역할별로 분리할 수 있습니다. 이 글에서는 core, spring-boot, demo 3개 모듈을 기준으로 Maven 멀티 모듈 프로젝트를 어떻게 설계하고, 각 pom.xml을 어떻게 작성하며, 실무에서는 어떤 기준으로 모듈을 나누는지 설명합니다.
Maven 공식 문서에서도 여러 모듈을 하나의 상위 POM에서 관리하는 구조를 multi-module 또는 aggregator project라고 설명합니다. 상위 POM은 pom 패키징을 사용하고, 하위 모듈을 <modules>에 등록하는 방식입니다.
Java 17 Maven 멀티 모듈 구조란?
Maven 멀티 모듈은 하나의 루트 프로젝트 아래에 여러 Maven 프로젝트를 하위 모듈로 두는 방식입니다.
예를 들어 다음과 같이 구성할 수 있습니다.
my-java17-multi-module
├── pom.xml
├── core
│ ├── pom.xml
│ └── src
│ └── main
│ └── java
├── spring-boot
│ ├── pom.xml
│ └── src
│ └── main
│ └── java
└── demo
├── pom.xml
└── src
└── main
└── java
각 모듈의 역할은 다음과 같이 나눌 수 있습니다.
| core | 공통 도메인, 유틸, 비즈니스 로직 |
| spring-boot | 실제 Spring Boot 실행 애플리케이션 |
| demo | 기능 테스트, 샘플 실행, 개발용 데모 코드 |
왜 core, spring-boot, demo로 나누는가?
단일 Spring Boot 프로젝트로 만들면 처음에는 단순합니다.
하지만 프로젝트가 커지면 다음 문제가 생깁니다.
src/main/java
├── controller
├── service
├── domain
├── util
├── config
└── demo
처음에는 괜찮아 보이지만 시간이 지나면 공통 로직, 실행 코드, 테스트용 코드가 섞입니다.
예를 들어 core에 있어야 할 계산 로직이 Controller에 들어가거나, 테스트용 샘플 코드가 운영 애플리케이션에 포함되는 문제가 생길 수 있습니다.
그래서 실무에서는 다음처럼 관심사를 분리합니다.
core -> Spring에 의존하지 않는 순수 Java 로직
spring-boot -> Spring Boot 실행, API, 설정
demo -> 사용 예제, 샘플 실행 코드
이렇게 구성하면 core는 다른 프로젝트에서도 재사용할 수 있고, spring-boot는 실행에 집중하며, demo는 운영 코드와 분리된 실험 공간으로 사용할 수 있습니다.
전체 프로젝트 구조
예제 프로젝트 이름은 java17-maven-modules로 하겠습니다.
java17-maven-modules
├── pom.xml
├── core
│ ├── pom.xml
│ └── src/main/java/com/example/core
│ └── Calculator.java
├── spring-boot
│ ├── pom.xml
│ └── src/main/java/com/example/app
│ ├── SpringBootApplicationMain.java
│ └── CalculatorController.java
└── demo
├── pom.xml
└── src/main/java/com/example/demo
└── DemoMain.java
루트 pom.xml 작성
루트 pom.xml은 하위 모듈을 관리하는 부모 역할을 합니다.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>java17-maven-modules</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>core</module>
<module>spring-boot</module>
<module>demo</module>
</modules>
<properties>
<java.version>17</java.version>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.boot.version>3.3.0</spring.boot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
중요한 부분은 다음입니다.
<packaging>pom</packaging>
루트 프로젝트는 직접 jar를 만드는 프로젝트가 아니라 하위 모듈을 묶는 역할이므로 pom 패키징을 사용합니다.
<modules>
<module>core</module>
<module>spring-boot</module>
<module>demo</module>
</modules>
이 설정을 통해 Maven은 루트에서 빌드할 때 하위 모듈을 함께 빌드합니다.
Spring Boot Maven Plugin은 실행 가능한 jar 또는 war 패키징, 실행, 빌드 정보 생성 등을 지원하는 플러그인입니다. 실제 실행 모듈인 spring-boot에만 적용하는 것이 일반적입니다.
core 모듈 만들기
core 모듈은 Spring Boot에 의존하지 않는 순수 Java 모듈로 구성합니다.
core/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>java17-maven-modules</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>core</artifactId>
<packaging>jar</packaging>
</project>
Calculator.java
package com.example.core;
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int multiply(int a, int b) {
return a * b;
}
}
core 모듈에는 Controller, Service, Repository 같은 Spring 전용 코드를 넣지 않는 것이 좋습니다.
실무에서는 다음과 같은 코드를 core에 둡니다.
| 공통 유틸 | 날짜 계산, 문자열 처리, 암호화 |
| 도메인 모델 | User, Order, Payment |
| 비즈니스 규칙 | 가격 계산, 상태 변경 규칙 |
| 예외 클래스 | CustomException, ErrorCode |
| 공통 DTO | 내부 모듈 간 공유 객체 |
spring-boot 모듈 만들기
spring-boot 모듈은 실제 실행되는 Spring Boot 애플리케이션입니다.
이 모듈은 core 모듈을 의존성으로 추가합니다.
spring-boot/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>java17-maven-modules</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>spring-boot</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
</plugin>
</plugins>
</build>
</project>
SpringBootApplicationMain.java
package com.example.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootApplicationMain {
public static void main(String[] args) {
SpringApplication.run(SpringBootApplicationMain.class, args);
}
}
CalculatorController.java
package com.example.app;
import com.example.core.Calculator;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CalculatorController {
private final Calculator calculator = new Calculator();
@GetMapping("/add")
public int add() {
return calculator.add(10, 20);
}
}
실행 후 다음 URL로 접근하면 됩니다.
http://localhost:8080/add
결과는 다음과 같습니다.
30
demo 모듈 만들기
demo 모듈은 Spring Boot 서버를 띄우지 않고 core 기능을 빠르게 테스트하거나 샘플 코드를 실행하는 용도로 사용할 수 있습니다.
demo/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>java17-maven-modules</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>demo</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>core</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
DemoMain.java
package com.example.demo;
import com.example.core.Calculator;
public class DemoMain {
public static void main(String[] args) {
Calculator calculator = new Calculator();
int result = calculator.multiply(5, 4);
System.out.println("result = " + result);
}
}
실행 결과는 다음과 같습니다.
result = 20
빌드 방법
루트 프로젝트에서 다음 명령어를 실행합니다.
mvn clean package
전체 모듈이 순서대로 빌드됩니다.
특정 모듈만 빌드하고 싶다면 다음처럼 실행할 수 있습니다.
mvn clean package -pl spring-boot -am
옵션 의미는 다음과 같습니다.
| -pl spring-boot | spring-boot 모듈만 선택 |
| -am | spring-boot가 의존하는 모듈도 함께 빌드 |
즉, spring-boot가 core를 참조하고 있다면 core도 같이 빌드됩니다.
Spring Boot 실행 방법
루트에서 실행할 경우 다음 명령어를 사용할 수 있습니다.
mvn -pl spring-boot spring-boot:run
또는 패키징 후 jar 파일을 실행할 수 있습니다.
java -jar spring-boot/target/spring-boot-1.0.0.jar
자주 발생하는 오류
1. Could not find artifact com.example:core
Could not find artifact com.example:core:jar:1.0.0
이 오류는 spring-boot 모듈이 core 모듈을 찾지 못할 때 발생합니다.
해결 방법은 루트에서 빌드하는 것입니다.
mvn clean install
또는 의존 모듈까지 함께 빌드합니다.
mvn clean package -pl spring-boot -am
2. Source option 5 is no longer supported
Source option 5 is no longer supported. Use 7 or later.
Java 버전 설정이 제대로 적용되지 않았을 때 발생할 수 있습니다.
루트 pom.xml에 다음 설정을 넣습니다.
<properties>
<maven.compiler.release>17</maven.compiler.release>
</properties>
3. no main manifest attribute
no main manifest attribute
Spring Boot 실행 jar로 패키징되지 않았을 때 발생할 수 있습니다.
spring-boot 모듈에 다음 플러그인이 있는지 확인합니다.
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
</plugin>
</plugins>
</build>
실무에서 추천하는 의존성 방향
멀티 모듈에서 가장 중요한 것은 의존성 방향입니다.
추천 구조는 다음과 같습니다.
spring-boot -> core
demo -> core
core -> 아무 모듈에도 의존하지 않음
반대로 아래 구조는 피하는 것이 좋습니다.
core -> spring-boot
core가 Spring Boot에 의존하기 시작하면 재사용성이 떨어지고, 나중에 배치 서버나 CLI 프로그램에서 core만 가져다 쓰기 어려워집니다.
최종 구조 정리
java17-maven-modules
├── pom.xml
├── core
│ ├── pom.xml
│ └── src/main/java/com/example/core/Calculator.java
├── spring-boot
│ ├── pom.xml
│ └── src/main/java/com/example/app
│ ├── SpringBootApplicationMain.java
│ └── CalculatorController.java
└── demo
├── pom.xml
└── src/main/java/com/example/demo/DemoMain.java
Java 17 Maven 멀티 모듈 프로젝트에서 core, spring-boot, demo 구조를 사용하면 공통 로직과 실행 애플리케이션을 분리할 수 있습니다. 특히 core 모듈을 순수 Java 중심으로 유지하면 Spring Boot 외부에서도 재사용할 수 있고, 프로젝트가 커져도 유지보수하기 쉬운 구조를 만들 수 있습니다.
FAQ
Q1. core 모듈에도 Spring 의존성을 넣어도 되나요?
가능은 하지만 추천하지 않습니다. core는 가능한 순수 Java 모듈로 유지하는 것이 좋습니다. 그래야 다른 애플리케이션에서도 쉽게 재사용할 수 있습니다.
Q2. spring-boot 모듈 이름을 꼭 spring-boot로 해야 하나요?
아닙니다. app, api, server, web 같은 이름을 사용해도 됩니다. 다만 역할이 명확해야 합니다.
Q3. demo 모듈은 꼭 필요한가요?
필수는 아닙니다. 하지만 라이브러리성 프로젝트나 공통 모듈을 만들 때는 demo 모듈이 있으면 사용 예제를 분리해서 관리하기 좋습니다.
Q4. Spring Boot Maven Plugin은 모든 모듈에 넣어야 하나요?
보통은 실행 가능한 Spring Boot 애플리케이션 모듈에만 넣습니다. core처럼 단순 jar 모듈에는 넣지 않아도 됩니다.
