본문으로 건너뛰기
개발 머꼬
개발 노트Java
hohyeon.dev14

NullPointerException 메시지에서 어느 값이 null인지 바로 찾는 법

  • #Debugging
  • #Engineering Note
  • #Java

문제 발생

운영 로그에 NPE가 찍혔는데 한 줄에 호출이 여러 개 이어져 있어서, 어느 것이 null인지 알 수 없었습니다.

String city = order.getCustomer().getAddress().getCity();
// java.lang.NullPointerException
//   at OrderService.describe(OrderService.java:42)

getCustomer()가 null인지, getAddress()가 null인지 스택 트레이스만으로는 판단할 수 없어서 로그를 추가하고 재현을 기다려야 했습니다.

원인 분석

NPE는 자바독이 열거하는 다섯 가지 경우에 발생합니다.

  • null 객체의 인스턴스 메서드 호출
  • null 객체의 필드 접근·수정
  • null을 배열처럼 보고 길이를 구할 때
  • null의 배열 요소 접근·수정
  • null을 Throwable던질 때

예전에는 이 중 무엇인지, 어느 식이었는지를 메시지가 알려 주지 않았습니다. JDK 14의 JEP 358(Helpful NullPointerExceptions) 이 이를 바꿨습니다. 자바독의 구현 노트에 따르면, 생성자에 명시적 메시지를 넘기지 않았고 내부 정보를 얻을 수 있다면 어떤 null 참조였는지 자세히 설명하는 문구가 반환됩니다.

그래서 지금은 메시지가 이렇게 나옵니다.

java.lang.NullPointerException: Cannot invoke "Address.getCity()" because the return value of
"Customer.getAddress()" is null

"Customer.getAddress()의 반환값이 null이라서 Address.getCity()를 호출할 수 없다" — 범인이 문장 안에 있습니다.

해결 방안

  1. 메시지를 먼저 읽습니다. 이 문구는 "무엇을 하려다가(Cannot invoke ...)"와 "왜 못 했는지(because ... is null)" 두 부분으로 구성됩니다. 뒷부분이 고쳐야 할 자리입니다.
  2. 로그에 메시지가 잘리지 않게 합니다. 예외 클래스 이름만 찍고 메시지를 버리는 로깅 설정이라면 이 정보가 통째로 사라집니다. log.error("...", e)처럼 예외 객체를 그대로 넘깁니다.
  3. 역직렬화된 예외에는 이 정보가 없습니다. 자바독이 명시하는 제약입니다. 원격 호출 결과로 받은 예외라면 메시지가 비어 있을 수 있습니다.
  4. 없으면 만들어 둡니다. 애초에 null이 오면 안 되는 자리라면 경계에서 막습니다. 메시지에 무엇이 잘못됐는지 담을 수 있습니다.
Objects.requireNonNull(customer, "customer는 null일 수 없습니다");
  1. 연쇄 호출을 줄이면 진단이 쉬워집니다. 메시지가 좋아졌다고 해도, 한 줄에 네 단계를 이어 붙이면 그만큼 실패 지점이 늘어납니다. 값이 없을 수 있는 단계는 Optional로 표현하거나 중간에 이름을 붙입니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.