* 이 게시물은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
(자바 버전 여행 ⑧)
모듈 시스템은 자바 9의 간판 기능인데, 정작 실무에서는 “안 써도 잘 되던데?” 하는 사람이 많다. 둘 다 맞는 말이다. 이번 편은 모듈이 왜 나왔고, 그런데 왜 대부분은 안 쓰는지까지 솔직하게 정리한다.
JAR 지옥과 뻥 뚫린 캡슐화
자바 8까지는 라이브러리를 그냥 클래스패스에 쭉 늘어놓았다. 문제가 둘이었다. 하나는 같은 클래스가 여러 JAR에 들어 있으면 어느 게 로딩될지 알기 어려운 JAR 지옥. 또 하나는 public이면 패키지 밖 누구나 갖다 쓸 수 있어서, 내부용 클래스도 사실상 다 공개되는 캡슐화의 구멍이었다.
모듈이 하는 일
모듈은 JAR에 이름표와 계약서를 붙인다. module-info.java 하나로 “나는 무엇에 의존하고(requires), 무엇을 공개하는가(exports)”를 선언한다. 선언하지 않은 패키지는 아예 밖에서 못 본다. 강한 캡슐화다.
// com.example.greeter 모듈의 module-info.java
module com.example.greeter {
exports com.example.greeter; // 이 패키지만 공개
// 나머지 내부 패키지는 숨겨진다
}
// app 모듈의 module-info.java
module com.example.app {
requires com.example.greeter; // greeter에 의존
}
JDK 자체가 모듈로 쪼개졌다
사실 이 기능의 진짜 수혜자는 JDK다. 예전엔 통짜였던 표준 라이브러리가 java.base, java.sql 처럼 잘게 나뉘었다. 덕분에 jlink로 내 앱이 쓰는 모듈만 담은 작은 런타임을 만들 수 있다. 도커 이미지를 줄일 때 요긴하다.
module-info.java — requires와 exports
모듈은 module-info.java로 선언한다. requires로 ‘내가 쓰는 모듈’을, exports로 ‘내가 공개하는 패키지’를 명시한다. exports 안 한 패키지는 public이어도 밖에서 못 쓴다 — 이게 클래스패스 시절에는 없던 ‘진짜 캡슐화’다. 리플렉션 접근까지 열려면 opens를 쓴다.
module com.cozlab.app {
requires java.net.http; // 이 모듈을 쓴다
exports com.cozlab.api; // 이 패키지만 공개
// opens com.cozlab.model; // 리플렉션 접근 허용(필요 시)
}
jlink·jpackage — 필요한 것만 담은 런타임
모듈의 실질적 이득 하나가 jlink다. 앱이 실제로 쓰는 모듈만 골라 ‘맞춤 JRE’를 만든다. 전체 JDK 대신 수십 MB로 줄어 컨테이너 이미지가 가벼워진다. jpackage로 설치본까지 만들 수 있다. 크기는 담는 모듈에 따라 다르지만, java.base+몇 개 수준의 단순 앱이면 전체 JDK(수백 MB)가 수십 MB급 맞춤 런타임으로 줄어드는 게 보통이다 — 정확한 숫자는 jlink 실행 후 산출 폴더 크기로 바로 확인된다. 클라우드·데스크톱 배포에서 이 슬림화가 모듈의 매력적인 실전 이유다.
그런데 왜 다들 안 쓰나 — 현실
솔직히 대부분의 애플리케이션은 모듈 없이도 잘 돈다. 라이브러리 생태계가 아직 자동 모듈(automatic module)에 걸쳐 있고, 스프링 같은 프레임워크가 리플렉션을 많이 써 강한 캡슐화와 마찰이 있다. 그래서 앱은 클래스패스로 두고, JDK·라이브러리 저자만 모듈을 적극 쓰는 게 지금의 풍경이다. ‘안 써도 된다’와 ‘알아둘 값어치가 있다’가 둘 다 맞다.
클래스패스 앱이 모듈화된 라이브러리를 만날 때의 실무 함정도 하나 알아두자 — automatic module 이름 충돌이다. module-info가 없는 JAR를 모듈 경로에 두면 파일명에서 모듈 이름이 자동 유도되는데, 두 JAR가 같은 이름으로 유도되거나 나중에 라이브러리가 정식 모듈 이름을 선언하면서 이름이 바뀌면 requires가 깨진다. 라이브러리 저자들이 Automatic-Module-Name 매니페스트를 먼저 박아두는 이유가 이것이고, 앱 개발자 입장에선 ‘모듈 경로엔 정식 모듈만, 나머지는 클래스패스에’가 무난한 회피책이다.
그래도 알아둘 값어치 — 강한 캡슐화
모듈을 직접 안 쓰더라도, 자바 17의 ‘강한 캡슐화’는 이 모듈 시스템의 연장선이다. sun.* 같은 내부 API 접근이 막힌 것(11편의 마이그레이션 함정)도 모듈 경계 덕이다. 그래서 8→17 이동을 제대로 이해하려면 이 모듈 개념을 한 번은 짚어둬야 한다. 안 쓰는 기능이라도, 그 그림자는 최신 자바 곳곳에 드리워 있다.
모듈로 자바 9의 큰 그림을 봤으니, 다음 편은 훨씬 가볍고 매일 쓰게 되는 var(타입 추론)와 자바 9~10의 소소하지만 반가운 개선들이다.
자주 묻는 질문 (FAQ)
모듈 시스템을 꼭 써야 하나요?
대부분의 앱은 여전히 클래스패스로 잘 돌아갑니다. 모듈은 JDK나 큰 프레임워크·플랫폼에 주로 쓰이니 개념만 알아도 충분합니다.
jlink는 뭐에 쓰나요?
내 앱이 쓰는 모듈만 담은 작은 커스텀 런타임을 만듭니다. 도커 이미지 크기를 줄일 때 유용합니다.

답글 남기기