Maven "Could not resolve dependencies" 빌드 실패 원인 6가지와 settings.xml 해결법
어제까지 잘 되던 빌드가 갑자기 깨졌다면
콘솔에 빨갛게 찍힌 Failed to execute goal ... Could not resolve dependencies 한 줄을 그대로 복사해서 검색하다 들어오셨을 겁니다. 결론부터 말하면, 이 에러 메시지 자체는 "의존성을 못 가져왔다"는 결과만 알려줄 뿐 진짜 원인은 그 아래 몇 줄에 숨어 있습니다.
특히 사내 Nexus/Artifactory, 프록시, 사설 CA가 깔린 폐쇄망 환경에서는 코드 한 줄 안 건드렸는데도 빌드가 깨지는 일이 잦습니다. 이 글은 Maven 전용으로, settings.xml의 mirror/proxy/server 태그와 ~/.m2 구조, mvn 플래그만으로 원인을 5분 안에 특정하고 복구하는 데 집중합니다.
이 글의 범위: Maven CLI / settings.xml / 로컬 저장소(.m2) 중심. IDE 내장 빌드나 다른 빌드 도구 이슈는 다루지 않습니다.
먼저 에러를 정확히 읽기 — mvn -X로 진짜 원인 찾기
가장 흔한 실수가 맨 마지막 Failed to execute goal 줄만 보고 좌절하는 것입니다. 이 줄은 항상 똑같이 나옵니다. 우리가 봐야 할 건 그보다 위쪽의 Caused by 또는 Could not transfer artifact 줄입니다.
디버그 로그를 켜고 다시 돌려보세요.
# 전체 통신/저장소 접근 로그까지 출력
mvn -X clean installmvn -X를 켜면 어느 저장소(repository)로 어떤 URL에 접속을 시도했고, 무슨 이유로 실패했는지가 그대로 찍힙니다. 아래 키워드 중 어느 게 보이는지가 분기점입니다.
| 로그에서 보이는 문구 | 매핑되는 원인 | 한 줄 의미 |
|---|---|---|
Could not transfer artifact ... Connection refused / timed out | ② 프록시·방화벽 / ① 미러 주소 오타 | 저장소에 아예 못 닿음 |
PKIX path building failed / unable to find valid certification path | ③ 사설 인증서 SSL 실패 | TLS 핸드셰이크 거부 |
Could not find artifact ...:jar:1.2.3 in central (404) | ⑤ 버전 오류·SNAPSHOT 미배포 | 저장소엔 닿았지만 그 버전이 없음 |
Failed to read artifact descriptor / .lastUpdated 언급 | ④ 로컬 캐시 손상 | 망가진 캐시를 붙잡고 있음 |
Cannot access ... in offline mode | ⑥ -o 플래그 오용 | 오프라인인데 새 의존성 요구 |
충돌이 의심되면 의존성 트리부터 확인합니다.
# 전체 의존성 트리
mvn dependency:tree
# 특정 라이브러리가 어디서 끌려오는지, 버전 충돌이 어떻게 정리됐는지 추적
mvn dependency:tree -Dverbose -Dincludes=org.springframework:spring-core원인 ① 사내 Nexus/Artifactory 미러·settings.xml 오설정
Connection refused인데 URL이 Maven Central이 아니라 사내 Nexus 주소(nexus.회사.co.kr)라면, 십중팔구 settings.xml의 미러 설정 문제입니다. Nexus가 점검 중이거나, 주소가 바뀌었거나, 인증 정보가 만료됐을 때 발생합니다.
~/.m2/settings.xml을 열어 <mirrorOf>가 무엇을 가리키는지부터 확인하세요. central만 미러하는지, *(전부)인지에 따라 동작이 완전히 달라집니다.
<!-- ~/.m2/settings.xml : 사내 Nexus 미러 + 인증 예제 -->
<settings>
<servers>
<!-- mirror의 id와 반드시 동일해야 인증이 적용됨 -->
<server>
<id>company-nexus</id>
<username>${env.NEXUS_USER}</username> <!-- 평문 대신 환경변수 권장 -->
<password>${env.NEXUS_PASS}</password>
</server>
</servers>
<mirrors>
<mirror>
<id>company-nexus</id> <!-- server의 id와 일치 -->
<name>Company Nexus</name>
<url>https://nexus.example.co.kr/repository/maven-public/</url>
<mirrorOf>*</mirrorOf> <!-- 모든 저장소 요청을 Nexus로 우회 -->
</mirror>
</mirrors>
</settings>핵심은 <server>의 id와 <mirror>의 id가 정확히 같아야 인증 헤더가 붙는다는 점입니다. 401/403이 뜬다면 이 id 불일치나 비밀번호 만료를 먼저 의심하세요.
원인 ② 회사 프록시/방화벽으로 Maven Central 차단
<mirrorOf>가 central만이라 Maven Central로 직접 나가야 하는데 Connection timed out이 뜬다면, 회사 프록시를 안 거쳐서 방화벽에 막힌 경우입니다. 먼저 차단 여부를 진단합니다.
# 프록시 없이 직접 닿는지 확인 (timeout이면 차단)
curl -I https://repo.maven.apache.org/maven2/
# 프록시를 거치면 되는지 확인
curl -I -x http://proxy.example.co.kr:8080 https://repo.maven.apache.org/maven2/프록시 경유 시 200이 뜨면 settings.xml에 프록시를 등록합니다.
<!-- ~/.m2/settings.xml : 프록시 설정 예제 -->
<settings>
<proxies>
<proxy>
<id>company-proxy</id>
<active>true</active> <!-- false면 무시됨, 켜는 걸 잊지 말 것 -->
<protocol>http</protocol>
<host>proxy.example.co.kr</host>
<port>8080</port>
<username>proxyUser</username> <!-- 인증 프록시일 때만 -->
<password>proxyPass</password>
<!-- 사내 Nexus는 프록시 제외(직접 접속) -->
<nonProxyHosts>nexus.example.co.kr|localhost|127.0.0.1</nonProxyHosts>
</proxy>
</proxies>
</settings><nonProxyHosts>를 빠뜨리면 사내 Nexus까지 외부 프록시로 보내려다 또 실패하니 주의하세요. 참고로 Maven Central은 HTTP 접근을 막고 HTTPS만 허용하므로, 오래된 설정의 http://...central URL은 모두 https://로 바꿔야 합니다.
원인 ③ 사설 인증서로 인한 PKIX/SSL 핸드셰이크 실패
PKIX path building failed 또는 unable to find valid certification path to requested target가 보이면 SSL 신뢰 문제입니다. 사내 프록시/Nexus가 사설 CA로 서명한 인증서를 쓰는데, JDK의 신뢰 저장소(cacerts)에 그 CA가 없어서 거부되는 상황입니다. 제로트러스트·폐쇄망 도입이 늘면서 가장 빠르게 증가하는 케이스입니다.
핵심 해결은 사설 CA 인증서를 JDK에 임포트하는 것입니다.
# 사내 CA 인증서를 JDK 신뢰 저장소에 등록
keytool -import -trustcacerts -alias company-ca \
-file company-ca.crt \
-keystore "$JAVA_HOME/lib/security/cacerts" \
-storepass changeitPKIX 오류의 인증서 추출 방법, 체인 구성, JDK 버전별 경로 차이 등 상세 절차는 별도의 PKIX 트러블슈팅 글에서 깊게 다루므로 여기서는 핵심만 짚고 넘어갑니다.
원인 ④ 손상된 로컬 저장소 캐시(~/.m2/repository)
저장소엔 잘 닿는데 특정 라이브러리만 계속 실패하고 로그에 .lastUpdated가 보인다면, 다운로드가 중간에 끊겨 캐시가 망가진 경우입니다. Maven은 실패 기록(*.lastUpdated)을 남겨두고 한동안 재시도를 안 합니다.
전체 .m2를 통째로 지우기 전에, 실패 마커만 정리하는 게 훨씬 빠릅니다.
# 1) 실패 마커만 삭제 (가장 가볍고 안전)
find ~/.m2 -name "*.lastUpdated" -delete
# 2) 특정 의존성만 정리 후 재다운로드
mvn dependency:purge-local-repository -Dinclude=org.springframework:spring-core
# 3) 그래도 안 되면 강제 업데이트로 재시도
mvn -U clean install전체 rm -rf ~/.m2/repository는 최후의 수단입니다. 수 GB를 다시 받아야 해서 CI에서는 특히 비효율적입니다.
원인 ⑤ 버전 충돌·존재하지 않는 버전·SNAPSHOT 미배포
Could not find artifact ...:jar:1.2.3 in central (404)처럼 404가 명확히 보이면 통신 문제가 아닙니다. 저장소엔 잘 닿았는데 그 좌표(groupId:artifactId:version)가 실제로 없는 것입니다. 흔한 3가지 원인은:
- 오타나 잘못된 버전 번호 (예: 존재하지 않는
1.2.3) - 동료가
mvn install만 하고 Nexus에deploy안 한 SNAPSHOT 미배포 - 다른 라이브러리가 끌어온 추이적(transitive) 의존성의 버전 충돌
# 어떤 라이브러리가 문제의 버전을 끌고 오는지 정확히 추적
mvn dependency:tree -Dverbose -Dincludes=com.example:problem-libConnection refused(②)와 404(⑤)를 구분하는 게 핵심입니다. 전자는 "문을 못 열었다", 후자는 "문은 열었는데 물건이 없다"입니다.
원인 ⑥ -U·-o 플래그 오용
마지막으로 의외로 자주 발목 잡는 게 플래그입니다.
-o(offline): 오프라인 모드. 새 의존성을 못 받는데 캐시에 없으면Cannot access ... in offline mode가 납니다. 습관적으로 켜두지 마세요.-U(update): SNAPSHOT을 강제로 다시 받습니다. CI에서 최신 SNAPSHOT을 보장할 때 유용하지만, 매 빌드마다 켜면 불필요한 네트워크 호출로 느려지고 간헐적 실패를 부릅니다.
# SNAPSHOT 최신본이 필요할 때만
mvn -U clean install5분 진단 플로우차트
에러 문구만 들고 아래 순서대로 내려오면 자기 케이스를 찾을 수 있습니다.
- 로그에
PKIX또는valid certification path가 있는가? → 예: 원인 ③ (인증서 임포트) 404또는Could not find artifact가 있는가? → 예: 원인 ⑤ (버전·SNAPSHOT 확인)Connection refused / timed out이고 실패 URL이 사내 Nexus인가? → 예: 원인 ① (미러·인증 점검)Connection timed out이고 URL이 Maven Central인가? → 예: 원인 ② (프록시 설정).lastUpdated언급이 있거나 특정 1~2개만 실패하는가? → 예: 원인 ④ (find ~/.m2 -name "*.lastUpdated" -delete)offline mode문구가 있는가? → 예: 원인 ⑥ (-o제거)
위 6단계로 안 잡히면, settings.xml을 임시로 비활성화하고 순정 환경에서 한 번 돌려 환경 변수를 분리해 보세요.
실무에서 느낀 점
경험상 폐쇄망 프로젝트에서 신규 입사자 PC 빌드 실패의 80%는 ①과 ③ 두 가지였습니다. 그래서 저희 팀은 검증된 settings.xml과 사내 CA 인증서를 묶어 온보딩 스크립트로 자동 배포했더니 "빌드가 안 돼요" 문의가 거의 사라졌습니다. 개인이 매번 디버깅하는 것보다 표준 settings.xml을 형상관리하는 것이 훨씬 비용이 쌉니다.
재발 방지 체크리스트
settings.xml을 팀 표준으로 형상관리하고, 비밀번호는 환경변수로 분리- CI(GitHub Actions/Jenkins)에서
~/.m2/repository를 캐시하되,*.lastUpdated는 캐시 키에서 제외 -U는 SNAPSHOT 갱신이 꼭 필요한 잡에서만, 일반 빌드는 끄기- 모든 저장소 URL을
https://로 통일 (Maven Central HTTP 차단 대응) - 사내 CA 인증서를 JDK 이미지/온보딩 스크립트에 미리 포함
자주 묻는 질문 (FAQ)
Q. Could not resolve dependencies와 Failed to execute goal은 다른 에러인가요?
A. 사실상 한 묶음입니다. Failed to execute goal은 플러그인 실행이 멈췄다는 상위 메시지이고, Could not resolve dependencies가 그 직접 원인입니다. 진짜 원인은 항상 그 아래 Caused by나 Could not transfer artifact 줄에서 확인하세요.
Q. ~/.m2/repository를 통째로 지워도 되나요?
A. 됩니다만 최후의 수단입니다. 먼저 find ~/.m2 -name "*.lastUpdated" -delete로 실패 마커만 지우거나 mvn dependency:purge-local-repository로 문제 의존성만 정리하세요. 전체 삭제는 수 GB 재다운로드를 유발해 CI에서 특히 비효율적입니다.
Q. 404와 Connection refused는 어떻게 구분하나요? A. 404는 "저장소엔 닿았는데 그 버전이 없다"(원인 ⑤), Connection refused/timed out은 "저장소에 아예 못 닿았다"(원인 ①·②)입니다. 404면 버전·SNAPSHOT 배포 여부를, refused면 미러 주소와 프록시 설정을 점검하세요.
이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.
댓글
불러오는 중...