맥에서 자바를 여러 버전 쓰다 보면 어느 순간 java -version과 IDE가 서로 다른 JDK를 가리키는 상황을 만난다. 프로젝트 하나는 17이 필요하고 다른 하나는 아직 8에 묶여 있는데, 여기에 Homebrew로 깔아둔 JDK와 예전에 인스톨러로 넣은 시스템 JDK까지 섞이면 JAVA_HOME이 어디를 보는지 매번 헷갈린다. 예전에 .zshrc에 JAVA_HOME을 손으로 갈아끼우던 글을 쓴 적이 있는데, 프로젝트가 서너 개만 넘어가도 그 방식은 금방 한계가 온다.
결론부터 말하자면, SDKMAN을 쓰면 JDK 여러 개를 한 곳에서 설치하고 .sdkmanrc 파일로 디렉터리에 들어갈 때마다 자동으로 버전을 바꿀 수 있다. Homebrew나 시스템 JDK와 충돌하는 것 같으면 대부분 PATH 순서와 JAVA_HOME 문제이고, 이건 SDKMAN에 관리를 넘기고 수동 설정을 걷어내면 정리된다. 아래에서 설치, 자동 전환, 충돌 정리를 차례로 살펴본다.
1. SDKMAN이 무엇인가
SDKMAN은 자바 계열 SDK를 사용자 홈 디렉터리 안에서 설치하고 전환해주는 커맨드라인 도구다. 관리 대상은 JDK뿐 아니라 Gradle, Maven, Kotlin, Scala 같은 것도 포함한다. 설치물은 전부 ~/.sdkman/candidates/ 아래에 들어가고, 시스템 영역(/Library, /usr)은 건드리지 않는다. 그래서 sudo 없이 여러 버전을 깔고 지울 수 있다.
핵심은 셸이 시작될 때 SDKMAN이 PATH 앞쪽에 자기 candidates 경로를 끼워 넣는다는 점이다. java를 입력하면 이 경로가 먼저 잡히므로, SDKMAN이 활성화한 버전이 시스템에 뭐가 깔려 있든 우선한다. 이 동작을 이해하면 뒤에서 다룰 충돌 문제의 절반은 이미 설명된다.
2. 설치
맥에는 curl과 zip이 기본으로 있으므로 별도 준비물은 없다. 공식 설치 명령을 그대로 실행한다.
$ curl -s "https://get.sdkman.io" | bash
설치가 끝나면 안내대로 새 셸에서 초기화 스크립트를 불러온다. zsh를 쓰면 ~/.zshrc 끝에 다음 한 줄이 자동으로 추가된다.
export SDKMAN_DIR="$HOME/.sdkman" [[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"
터미널을 새로 열고 버전을 확인한다. 스크립트 버전과 네이티브 버전이 함께 나오면 설치가 된 것이다. 버전 숫자는 설치 시점에 따라 다를 수 있다.
$ sdk version SDKMAN! script: 5.19.0 native: 0.5.0
여기까지면 기본적인 설치는 됐다고 보면 된다.
3. JDK 여러 버전 설치와 전환
설치 가능한 자바 목록을 본다. Temurin, Amazon Corretto, Azul Zulu, GraalVM 등 벤더별로 정렬돼 나온다.
$ sdk list java
================================================================================
Vendor | Use | Version | Dist | Status | Identifier
--------------------------------------------------------------------------------
Temurin | | 21.0.4 | tem | | 21.0.4-tem
| | 17.0.12 | tem | | 17.0.12-tem
| | 11.0.24 | tem | | 11.0.24-tem
Corretto | | 21.0.4 | amzn | | 21.0.4-amzn
...
맨 오른쪽 Identifier가 설치할 때 쓰는 정확한 이름이다. 벤더를 딱히 가리지 않는다면 Temurin(-tem)이 무난하다. 필요한 버전을 식별자로 설치한다.
$ sdk install java 21.0.4-tem $ sdk install java 17.0.12-tem $ sdk install java 11.0.24-tem
처음 설치한 버전은 자동으로 기본값(default)이 된다. 현재 셸에서만 잠깐 다른 버전으로 바꾸려면 use, 시스템 전역 기본값을 바꾸려면 default를 쓴다.
$ sdk use java 17.0.12-tem Using java version 17.0.12-tem in this shell. $ sdk default java 21.0.4-tem Default java version set to 21.0.4-tem
use는 그 터미널 창을 닫으면 사라지고, default는 새로 여는 셸에도 적용된다. 이 둘의 차이를 헷갈리면 "분명 바꿨는데 새 터미널에서 원래대로 돌아온다"는 상황을 만난다. 그때는 use만 했을 가능성이 높다.
확인은 늘 같은 명령으로 한다.
$ java -version openjdk version "17.0.12" 2024-07-16 OpenJDK Runtime Environment Temurin-17.0.12+7 (build 17.0.12+7) OpenJDK 64-Bit Server VM Temurin-17.0.12+7 (build 17.0.12+7, mixed mode)
4. .sdkmanrc로 프로젝트별 자동 전환
.sdkmanrc는 프로젝트 최상위 디렉터리에 두는 설정 파일이다. 그 프로젝트에서 어떤 SDK 버전을 쓸지 적어두면, 디렉터리에 들어갈 때 SDKMAN이 그 버전으로 맞춰준다. 팀원이 같은 저장소를 받아도 동일한 JDK를 쓰게 만드는 용도로 유용하다.
프로젝트 폴더로 이동한 뒤 현재 활성 버전으로 파일을 생성한다.
$ cd ~/work/my-service $ sdk env init .sdkmanrc created.
생성된 파일 내용은 다음과 같다. 필요하면 값을 직접 고친다.
$ cat .sdkmanrc # Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below java=17.0.12-tem
이 파일을 커밋해두면 다른 사람이 sdk env install 한 번으로 필요한 JDK를 채워 넣을 수 있다.
$ sdk env install
수동으로 적용할 때는 그 디렉터리에서 sdk env를 실행한다. 원래 기본값으로 되돌리려면 sdk env clear다.
$ sdk env Using java version 17.0.12-tem in this shell.
디렉터리에 들어가면 자동으로 바꾸기
매번 sdk env를 치는 게 번거로우면 자동 전환을 켠다. ~/.sdkman/etc/config를 열어 다음 값을 true로 바꾼다.
sdkman_auto_env=true
셸을 새로 열면 적용된다. 이제 .sdkmanrc가 있는 디렉터리로 cd하면 그 버전이 활성화되고, 디렉터리를 벗어나면 기본값으로 되돌아간다.
$ cd ~/work/my-service Using java version 17.0.12-tem in this shell. $ java -version openjdk version "17.0.12" 2024-07-16 ... $ cd ~ Restored java version to 21.0.4-tem (default)
이 자동 전환은 셸의 PROMPT_COMMAND 훅으로 동작한다. 프롬프트가 그려지기 직전에 현재 디렉터리에 .sdkmanrc가 있는지 보고, 있으면 sdk env를 실행하는 방식이다. 별도의 신뢰 확인이나 승인 절차는 없다. auto_env를 켜두면 해당 파일이 있는 디렉터리에서 조건 없이 적용된다. 그래서 신뢰할 수 없는 저장소를 받아 열 때는 .sdkmanrc 내용을 한 번 확인하는 습관이 낫다.
5. Homebrew, 시스템 JDK와의 충돌 잡기
맥에서 자바가 꼬이는 대부분의 원인은 SDKMAN 밖에서 설치된 JDK가 PATH나 JAVA_HOME을 붙잡고 있는 경우다. 먼저 지금 무엇이 잡히는지 확인한다.
$ which java /Users/you/.sdkman/candidates/java/current/bin/java $ echo $JAVA_HOME /Users/you/.sdkman/candidates/java/current
정상이면 위처럼 둘 다 ~/.sdkman/candidates/java/current를 가리킨다. 만약 /opt/homebrew/opt/openjdk/...나 /Library/Java/JavaVirtualMachines/...가 나온다면 다른 설치물이 우선순위를 가로챈 것이다.
macOS의 java_home 심링크
맥에는 /usr/bin/java가 실제 JDK를 직접 가리키지 않고 /usr/libexec/java_home이라는 도구를 통해 시스템에 등록된 JDK를 찾는 구조가 있다. 인스톨러로 넣은 Oracle JDK나 Temurin pkg는 /Library/Java/JavaVirtualMachines/에 등록된다. 무엇이 등록돼 있는지 이 명령으로 본다.
$ /usr/libexec/java_home -V
Matching Java Virtual Machines (2):
21.0.4 (arm64) "Eclipse Adoptium" - "OpenJDK 21.0.4" /Library/Java/JavaVirtualMachines/temurin-21.jdk/...
17.0.9 (arm64) "Oracle Corporation" - "Java SE 17.0.9" /Library/Java/JavaVirtualMachines/jdk-17.jdk/...
SDKMAN이 설치한 JDK는 여기에 등록되지 않는다. 그래서 두 관리 체계가 공존하면 셸에서는 SDKMAN 버전이, java_home을 참조하는 일부 도구에서는 시스템 버전이 잡히는 엇갈림이 생긴다. 관리를 SDKMAN으로 통일하기로 했으면 시스템 등록 JDK는 남겨두더라도 JAVA_HOME은 SDKMAN 쪽으로 고정하는 게 깔끔하다.
수동 JAVA_HOME 설정을 걷어낸다
가장 흔한 사고는 .zshrc나 .zprofile에 예전에 넣어둔 JAVA_HOME export가 살아 있는 경우다. SDKMAN 초기화 줄보다 뒤에 이런 줄이 있으면 SDKMAN 설정을 덮어쓴다.
# 이런 줄이 있으면 지운다 export JAVA_HOME=$(/usr/libexec/java_home -v 17) export PATH="/opt/homebrew/opt/openjdk/bin:$PATH"
이 줄들을 지우고 JAVA_HOME 관리는 SDKMAN에 맡긴다. SDKMAN은 버전을 전환할 때마다 JAVA_HOME을 알아서 현재 버전에 맞춰준다. 셸 설정에서 자바 관련 PATH, JAVA_HOME 조작을 전부 제거하고 SDKMAN 초기화 줄만 남기는 것이 목표다.
Homebrew로 깐 openjdk가 있다면, 자바 실행 파일을 시스템에 링크하는 brew link를 걸어두지 않는 편이 충돌을 줄인다. 이미 링크했다면 해제한다.
$ brew unlink openjdk
정리한 뒤 새 터미널을 열고 다시 which java와 echo $JAVA_HOME이 둘 다 ~/.sdkman 경로를 가리키는지 확인한다. 여기까지 맞으면 IDE에도 같은 JDK를 물려주면 된다. IntelliJ IDEA라면 프로젝트 SDK 경로로 ~/.sdkman/candidates/java/<버전>을 직접 지정한다.
6. 정리
맥에서 JDK를 여러 개 쓴다면 SDKMAN 한 곳으로 설치와 전환을 모으고, 프로젝트마다 .sdkmanrc를 커밋해 자동 전환을 거는 방식이 관리 부담이 가장 적다. 충돌이 의심되면 which java와 echo $JAVA_HOME부터 확인하고, 셸 설정에 남은 수동 JAVA_HOME export와 Homebrew 링크를 걷어내는 것으로 대부분 해결된다. 시스템에 인스톨러 JDK가 등록돼 있어도 JAVA_HOME만 SDKMAN 쪽으로 고정하면 엇갈림은 사라진다.