도입
Java를 배울 때 가장 먼저 마주치는 명령 중 하나가 바로 javac입니다. 많은 입문자는 이것을 단순히 “.java 파일을 .class 파일로 바꾸는 명령” 정도로 이해하지만, 실제 javac는 훨씬 더 많은 역할을 합니다.
Oracle의 공식 매뉴얼에 따르면 javac는 Java 언어로 작성된 module, package, type 선언이 들어 있는 소스 파일을 읽어 JVM에서 실행되는 class 파일로 컴파일합니다. 또한 단순 컴파일뿐 아니라 annotation processing도 직접 지원하며, 클래스패스·모듈패스·출력 디렉터리·대상 Java 릴리스·경고·디버그 정보까지 폭넓게 제어할 수 있습니다.
:contentReference[oaicite:1]{index=1}
필요성
Java 개발에서 소스 코드 작성은 시작일 뿐입니다. 실제로는 컴파일 단계에서 타입 오류, 접근 제한, 경고, annotation processing, 의존성 위치, 출력 경로 같은 여러 요소가 함께 결정됩니다. 따라서 javac를 이해하면 단순한 문법 학습을 넘어 Java 빌드 시스템의 중심 축을 이해할 수 있습니다.
- 컴파일 오류와 실행 오류를 구분할 수 있다.
- 클래스패스와 모듈패스 문제를 해석하기 쉬워진다.
--release,-d,-cp같은 핵심 옵션을 목적에 맞게 쓸 수 있다.- annotation processing이 빌드에 어떻게 개입하는지 이해할 수 있다.
- JDK 도구 전체에서
javac가 어떤 위치에 있는지 보이기 시작한다.
:contentReference[oaicite:2]{index=2}
정의
JDK 25 공식 문서의 이름 설명은 매우 직설적입니다. javac는 “read Java declarations and compile them into class files”라고 정의됩니다. 또한 jdk.compiler 모듈 문서에서는 이 모듈이 system Java compiler와 그 command line equivalent인 javac의 구현을 정의한다고 설명합니다.
:contentReference[oaicite:3]{index=3}
javac와 java의 차이
| 명령 | 무엇을 하나 | 출력 / 결과 |
|---|---|---|
| javac | Java 소스를 읽어 컴파일 | .class 파일 생성 |
| java | JVM 시작, 클래스 로드, main() 호출 |
프로그램 실행 |
즉, javac는 빌드 단계의 중심이고, java는 실행 단계의 중심입니다. 둘을 구분하지 못하면 “컴파일은 됐는데 왜 실행이 안 되지?” 같은 문제를 제대로 분리해서 보기 어렵습니다.
:contentReference[oaicite:4]{index=4}
컴파일 흐름
.java 소스 파일 ↓ javac ↓ .class 바이트코드 파일 ↓ java ↓ JVM 시작 ↓ 클래스 로드 + main() 호출 ↓ 프로그램 실행
공식 매뉴얼은 javac가 소스 파일을 읽어 class 파일로 바꾸고, java는 JVM을 시작해 지정한 클래스를 로드하고 그 클래스의 main() 메서드를 호출한다고 설명합니다. 이 흐름을 정확히 잡아 두면 Java 도구 체계가 훨씬 덜 헷갈립니다.
:contentReference[oaicite:5]{index=5}
입력과 출력
공식 매뉴얼에 따르면 javac는 소수의 소스 파일일 때는 파일 이름을 직접 나열해서 받고, 소스가 많을 때는 @filename 형식의 argument file을 통해 입력을 받을 수 있습니다. 또한 여러 소스 파일을 함께 컴파일할 때는 이를 그룹으로 처리하고, 선언 사이의 의존성을 자동으로 해결합니다.
기본 동작은 각 소스 파일에서 생성된 class 파일을 같은 디렉터리에 두는 것이지만, 공식 문서는 -d 옵션으로 별도의 출력 디렉터리를 지정하는 것을 권장합니다.
:contentReference[oaicite:6]{index=6}
핵심 옵션
| 옵션 | 역할 | 실무 감각 |
|---|---|---|
-d |
class 출력 디렉터리 지정 | 빌드 결과를 한 폴더로 분리 |
--class-path / -classpath / -cp |
클래스패스에 라이브러리 위치 지정 | 모듈이 아닌 라이브러리 의존성 연결 |
--module-path / -p |
모듈패스에 모듈형 라이브러리 위치 지정 | 모듈 시스템 기반 컴파일 |
--release |
지정한 Java SE 릴리스 규칙과 API 기준으로 컴파일 | 과거 버전 호환 타깃 빌드 |
-source / -target |
소스 언어 규칙 / 생성 class 타깃 제어 | --release와 섞어 쓰면 안 됨 |
-g |
디버그 정보 생성 | 디버깅 친화적 빌드 |
-Xlint |
권장 경고 활성화 | 컴파일 시 품질 점검 강화 |
-deprecation |
deprecated 사용 상세 표시 | 구버전 API 점검 |
@filename |
옵션과 파일 목록을 인자 파일로 전달 | 긴 커맨드 정리 |
:contentReference[oaicite:7]{index=7}
--release가 중요한 이유
공식 문서는 --release가 지정한 Java SE 릴리스의 언어 규칙에 따라 소스를 컴파일하고, 그 릴리스를 대상으로 하는 class 파일을 생성하며, 해당 릴리스의 Java SE와 JDK API를 기준으로 컴파일한다고 설명합니다. 또한 --release를 사용할 때는 -source나 -target을 함께 쓸 수 없다고 명시합니다.
:contentReference[oaicite:8]{index=8}
클래스패스와 모듈패스
공식 매뉴얼은 플랫폼 외 라이브러리를 참조할 때, 라이브러리 코드가 모듈이 아니라면 class path에 두고, 모듈이라면 module path에 두라고 설명합니다. 또한 class path와 module path 옵션은 상호 배타적이지는 않지만, 모듈 컴파일에서 class path를 함께 쓰는 것은 흔한 형태가 아니라고 안내합니다.
또한 --class-path, -classpath, -cp를 지정하지 않으면 기본 사용자 클래스패스는 CLASSPATH 환경 변수 값이거나, 그마저 없으면 현재 디렉터리입니다.
:contentReference[oaicite:9]{index=9}
Annotation Processing
Oracle 문서는 javac가 annotation processing을 직접 지원한다고 설명합니다. 처리 방식은 -processor, --processor-path, --processor-module-path 같은 옵션이나 -proc:full, -proc:only, -proc:none으로 제어할 수 있습니다.
또한 processor가 새로운 소스 파일을 생성하면, 그 새 소스를 다시 스캔해 다음 라운드의 annotation processing이 이어질 수 있습니다. 즉, javac는 단순 번역기라기보다 컴파일 단계의 코드 생성 파이프라인 일부를 담당하기도 합니다.
:contentReference[oaicite:10]{index=10}
자주 보는 실전 예시
javac Hello.java
javac -d out src/com/example/Hello.java
javac -cp libs/mylib.jar -d out src/com/example/App.java
javac --release 17 -d out src/com/example/App.java
javac @options @sources
이 다섯 가지 정도만 익혀도 입문, 실습, 라이브러리 참조, 하위 버전 호환 빌드, 긴 명령 관리까지 상당수 상황을 커버할 수 있습니다.
:contentReference[oaicite:11]{index=11}
API로도 쓸 수 있다
jdk.compiler 모듈 문서와 javac 도구 가이드는 javac를 API로도 호출할 수 있다고 설명합니다. JavaCompiler API는 가장 유연한 방식이고, ToolProvider.findFirst("javac")는 command-line equivalent 기능을 프로그램에서 얻는 방식입니다.
즉, IDE나 빌드 도구가 내부적으로 Java 컴파일을 수행할 때 꼭 외부 프로세스로 javac를 띄우지 않아도 되는 구조가 이미 JDK에 제공됩니다.
:contentReference[oaicite:12]{index=12}
환경 변수와 긴 명령 관리
공식 문서는 @filename 형식의 argument file을 사용하면 매우 긴 옵션과 소스 파일 목록을 관리하기 쉬워진다고 설명합니다. 또한 JDK_JAVAC_OPTIONS 환경 변수의 내용은 실제 명령줄 인수 앞에 prepend된다고 명시합니다.
즉, 큰 프로젝트에서 반복되는 컴파일 옵션이 길어질수록, 무조건 한 줄 커맨드에 몰아 쓰기보다 argfile이나 빌드 도구 설정으로 분리하는 습관이 훨씬 좋습니다.
:contentReference[oaicite:13]{index=13}
자주 하는 오해
- javac가 프로그램을 실행한다고 생각함 → 실행은 보통
java가 담당합니다. -d를 주지 않아 빌드 결과가 소스 폴더에 흩어짐 → 공식 문서도 별도 출력 디렉터리 사용을 권장합니다.--release와-source/-target를 함께 씀 → 공식적으로 함께 사용할 수 없습니다.- 모듈 라이브러리와 일반 라이브러리를 같은 방식으로 경로 지정함 → class path와 module path를 구분해야 합니다.
- annotation processing이 빌드에 영향을 준다는 점을 놓침 →
-proc,-processor-path계열을 함께 봐야 합니다. - 경고를 무시함 →
-Xlint,-deprecation이 문제를 더 빨리 드러내 줄 수 있습니다.
:contentReference[oaicite:14]{index=14}
공부 루틴
javac Hello.java로 가장 기본 컴파일을 먼저 본다.-d out으로 출력 위치를 분리해 본다.-cp로 외부 라이브러리 참조를 붙여 본다.--release로 하위 버전 타깃 개념을 익힌다.-Xlint와-g를 켜서 경고와 디버그 정보를 체험해 본다.- 필요하면
@argfile와 JavaCompiler API까지 확장한다.
:contentReference[oaicite:15]{index=15}
디버깅과 분석 포인트
-d를 빠뜨려 결과물이 예상 위치에 없는 것은 아닌지 본다.--release 설정을 먼저 의심한다.-proc 설정을 확인한다.-Xlint와 -deprecation 출력부터 읽어 본다.:contentReference[oaicite:16]{index=16}
요약
- ✅
javac는 Java 선언을 읽어 JVM용 class 파일로 컴파일한다. - ✅ 단순 컴파일뿐 아니라 annotation processing도 직접 지원한다.
- ✅
-d,-cp,--module-path,--release,-Xlint,-g같은 옵션이 실전 핵심이다. - ✅
--release는 특정 Java SE 릴리스를 목표로 컴파일할 때 중요한 옵션이며-source/-target과 함께 쓸 수 없다. - ✅
@argfile와JDK_JAVAC_OPTIONS로 긴 명령을 관리할 수 있다. - ✅
javac는 CLI 도구이면서 동시에 JavaCompiler API와 ToolProvider를 통해 프로그램 안에서도 호출할 수 있다.
:contentReference[oaicite:17]{index=17}