본문 바로가기
💻 트러블슈팅 (에러 해결)

자바 개발자의 영원한 숙적! NullPointerException (NPE) 원인과 스프링 부트 해결법 완벽 정리

by 데브프리 2026. 6. 25.

안녕하세요, 여러분의 든든한 IT 길잡이 데브프리입니다.

 

자바(Java)와 스프링 부트(Spring Boot)로 백엔드 개발을 시작하신 분이라면, 터미널 창을 새카맣게 뒤덮는 무시무시한 에러 로그들을 만나게 됩니다. 그 수많은 에러 중에서도 개발자를 가장 괴롭히고, 또 가장 자주 등장하는 에러가 하나 있습니다.

 

java.lang.NullPointerException: Cannot invoke "..." because "..." is null

 

개발자들 사이에서 일명 'NPE'라고 불리는 이 에러는, 프로그램이 돌아가다가 갑자기 픽 쓰러지게 만드는 주범입니다.

 

오늘은 이 지긋지긋한 NullPointerException이 도대체 왜 발생하는 것인지 그 원인을 짚어보고, 스프링 부트 환경에서 가장 흔하게 저지르는 실수 2가지와 그 해결 방법까지 확실하게 정리해 드리겠습니다!

 

1. NullPointerException, 도대체 넌 누구냐? (빈 상자 비유)

NullPointerException(NPE)은 단어 그대로 "비어있는(Null) 것을 가리키다(Pointer) 발생한 오류(Exception)"입니다. 메모리상에 존재하지 않는 객체를 사용하려고 할 때 자바가 던지는 비명 소리죠.

NullPointerException NPE 에러 빈 상자 비유
NPE는 로봇에게 '빈 상자'를 건네주고 "그 상자 안에 있는 리모컨 전원 좀 켜줘!"라고 명령했을 때 발생하는 에러입니다.

우리의 일상생활에 비유해 볼까요?

여러분이 친구에게 "내 가방(객체)에서 지갑 좀 꺼내줘(메서드 호출)!"라고 부탁했습니다. 그런데 알고 보니 가방 자체가 없었습니다. 친구는 허공에 손을 휘젓다가 당황하며 화를 냅니다. "가방이 없는데 어떻게 지갑을 꺼내!!"

 

자바에서도 똑같습니다. 변수 이름만 만들어 놓고 그 안에 실제 데이터(객체)를 넣어두지 않은 상태, 즉 null인 상태의 변수에 대고 "기능을 실행해!"라고 명령하면 발생하는 것이 바로 NPE입니다.

 

2. 스프링 부트에서 NPE가 발생하는 가장 흔한 2가지 이유

스프링 부트 환경에서 NPE가 발생했다면 십중팔구 아래 2가지 이유 중 하나입니다. 해결 원리는 간단합니다. 빈 상자를 채워주거나, 상자가 비어있는지 미리 확인하는 것입니다.

스프링 부트 의존성 주입 실패 에러 해결
스프링 부트에서 객체가 Null이 되는 가장 큰 이유는 파이프라인(의존성 주입)이 제대로 연결되지 않았기 때문입니다.

방법 ①: 의존성 주입(DI) 누락 - @Autowired나 어노테이션 빼먹기

컨트롤러에서 서비스를 가져다 써야 하는데, 스프링에게 "이 서비스 좀 연결해 줘!"라고 말하는 것을 깜빡한 경우입니다.

[잘못된 코드 - NPE 발생]

Java
 
@RestController
public class BoardController {
    
    // 🚨 객체 선언만 하고 스프링에게 주입해 달라고 하지 않음! (빈 상자 상태)
    private BoardService boardService; 

    @GetMapping("/board")
    public String getBoard() {
        // boardService가 null이므로 여기서 NPE 폭발!
        return boardService.getBoardList(); 
    }
}

[해결된 코드] 스프링이 객체를 알아서 채워주도록 @Autowired를 붙이거나, 실무에서 가장 권장하는 방식인 @RequiredArgsConstructor와 final 키워드를 사용해야 합니다.

Java
 
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor // 💡 final이 붙은 객체를 스프링이 알아서 채워줍니다!
public class BoardController {
    
    // 💡 final을 붙여 필수적으로 주입받도록 설정 (상자가 채워짐)
    private final BoardService boardService; 

    @GetMapping("/board")
    public String getBoard() {
        return boardService.getBoardList(); // 정상 작동!
    }
}

방법 ②: DB에서 가져온 데이터가 없을 때 (조회 실패)

게시글 상세 보기를 눌렀는데, 그 글 번호가 DB에 존재하지 않아 빈 데이터(null)를 가져왔을 때 발생합니다.

[해결된 코드 - Optional 사용] 이럴 때는 데이터가 없을 수도 있다는 것을 가정하고, 안전하게 방어벽을 쳐주어야 합니다. 최신 자바에서는 Optional을 사용해 이 문제를 우아하게 해결합니다.

Java
 
public Board getBoardDetail(Long id) {
    // 💡 findById는 데이터가 없을 수 있으므로 Optional로 감싸서 반환됩니다.
    // orElseThrow를 사용해 "데이터가 비어있으면(null) 에러 메시지를 띄워라!" 라고 안전하게 방어합니다.
    return boardRepository.findById(id)
            .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id=" + id));
}

 

3. 요약: 트러블슈팅 체크리스트

NPE가 발생했다면 당황하지 말고 아래 3가지를 체크하세요.

  1. 에러 로그의 첫 번째 줄(Line) 클릭하기: 에러 로그를 스크롤해서 맨 위로 올라가 보면 at com.데브프리.demo... (BoardController.java:25) 처럼 에러가 난 파일과 줄 번호가 친절하게 적혀 있습니다. 그 줄에 있는 객체 중 하나가 무조건 Null입니다.
  2. 스프링 빈(Bean) 등록 여부 체크: 객체를 사용하기 전에 @Autowired, @Service, @Component 같은 어노테이션을 빼먹지 않았는지 확인합니다.
  3. 데이터베이스 반환값 의심하기: 폼에서 넘어온 데이터나 DB에서 조회한 데이터가 애초에 비어있는 상태로 들어오지 않았는지 System.out.println이나 디버그 모드로 변수 값을 찍어봅니다.

 

마무리

화면을 붉게 물들이는 NullPointerException 때문에 머리를 쥐어뜯은 경험, 개발자라면 누구나 거치는 통과의례입니다.

 

하지만 NPE는 원인이 명확합니다. "내가 무언가를 쓰려고 했는데, 거기에 아무것도 없었다"는 뜻이니까요. 에러 로그가 가리키는 몇 번째 줄인지 정확히 찾아가서, 어떤 상자가 비어있는지만 확인하면 이 악명 높은 에러도 1분 안에 잡을 수 있습니다.

 

오늘 준비한 트러블슈팅 가이드가 여러분의 붉은 에러 창을 깨끗하게 지워주었기를 바랍니다. 오류를 고친 경험은 여러분의 가장 큰 자산이 됩니다. 막히는 에러가 있다면 언제든 댓글로 남겨주세요.

 

지금까지 데브프리였습니다. 감사합니다!