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