이 글에서는 안드로이드 개발에서 가장 흔하게 만나는 오류인 java.lang.NullPointerException을 정리한다. 실제 액티비티/프래그먼트 코드 예제와 함께 1) 언제/왜 발생하는지, 2) 어떻게 해결하는지, 3) 처음부터 어떻게 예방할 수 있는지를 중심으로 설명한다.

1. NullPointerException은 언제, 왜 발생할까?
NullPointerException(이하 NPE)은 참조 타입 변수가 null 인데, 그 변수로 메서드 호출이나 프로퍼티 접근을 할 때 발생하는 런타임 오류다. 안드로이드에서는 특히 액티비티/프래그먼트 생명주기, 뷰 바인딩, Intent/Bundle 값, 네트워크 비동기 콜백에서 자주 등장한다.
1.1 프래그먼트에서 뷰 바인딩이 null 인 경우
프래그먼트는 onCreateView()에서 뷰를 만들고, onDestroyView()에서 뷰를 파괴한다. 이 사이가 아닐 때 binding으로 뷰에 접근하면 null이어서 NPE가 난다.
// 잘못된 예시
class ProfileFragment : Fragment() {
private var binding: FragmentProfileBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentProfileBinding.inflate(inflater, container, false)
return binding!!.root
}
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
fun updateName(name: String) {
// 화면이 이미 사라진 뒤에 호출되면 binding == null 이라서 NPE 발생
binding!!.textName.text = name
}
}
- 비동기 콜백에서 프래그먼트가 이미 종료된 뒤에
updateName()을 호출하는 경우 - Navigation으로 다른 화면으로 이동한 뒤 이전 프래그먼트의 뷰를 건드리는 경우
onCreateView()~onDestroyView() 구간에서만 뷰에 접근해야 한다. 그 외에는 언제든 null 일 수 있다.1.2 Intent / Bundle / Arguments 값이 없을 때
다른 화면에서 putExtra/arguments로 값을 넘긴다고 “믿고” 코드를 짰지만, 실제로는 키 오타나 로직 변경으로 값이 안 넘어와서 null 인 경우에도 NPE가 터진다.
// 잘못된 예시
class DetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_detail)
val title = intent.getStringExtra("title") // null 일 수 있음
// title.toString() 은 "null" 이 되지만,
// 아래처럼 바로 length 를 접근하면 NPE
val length = title!!.length
findViewById<TextView>(R.id.textTitle).text = title
}
}
- 테스트용 진입 경로에서
putExtra("title", ...)를 안 넣은 경우 - 딥링크/푸시 알림 등 외부 인텐트에서는 해당 키가 아예 없는 경우
1.3 비동기 네트워크 응답에서 화면이 이미 사라진 경우
네트워크 요청을 보낸 뒤, 사용자가 뒤로가기나 화면 전환을 해서 액티비티/프래그먼트가 사라졌는데 콜백 안에서 뷰에 접근하면 뷰가 null 이라서 NPE가 발생한다.
// 잘못된 예시
api.getUserProfile { result ->
// 이 시점에 Activity 가 finish() 된 상태라면?
findViewById<TextView>(R.id.textName).text = result.name // NPE 가능
}
2. NullPointerException 해결 방법
이미 NPE가 발생했다면, 먼저 Logcat 에 찍힌 stack trace 로 정확한 라인 번호를 찾는 것부터 시작해야 한다. 그 줄에서 어떤 변수가 null 인지 파악한 뒤, 생명주기·데이터 흐름을 따라가면서 “왜 null 인지”를 역추적한다.
2.1 프래그먼트 ViewBinding 패턴으로 수정
프래그먼트에서는 아래 패턴이 사실상 표준이다. _binding은 nullable, 외부에서는 binding 게터로만 접근하도록 해서 생명주기 밖 접근을 빠르게 발견할 수 있다.
// 수정된 예시
class ProfileFragment : Fragment() {
private var _binding: FragmentProfileBinding? = null
private val binding get() = _binding
?: throw IllegalStateException("View binding is only valid between onCreateView and onDestroyView")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentProfileBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun updateName(name: String) {
binding.textName.text = name
}
}
- 뷰가 파괴된 상태에서 접근하면 NPE 대신
IllegalStateException으로 바로 문제를 인지 - 디버깅 시 “생명주기 밖에서 접근했다”는 사실이 명확해져 원인 파악이 쉬워진다
2.2 Intent / Bundle 값 읽을 때 안전하게 처리
외부에서 넘어올 수 있는 값(Intent, arguments 등)은 항상 null 일 수 있다고 가정하고 방어 코드를 넣어야 한다.
// 안전한 예시 1: 기본값 제공
val title: String = intent.getStringExtra("title") ?: "제목 없음"
textTitle.text = title
// 안전한 예시 2: 필수 값이면 없을 때 명확히 종료
val userId = intent.getStringExtra("userId")
if (userId == null) {
Toast.makeText(this, "잘못된 접근입니다.", Toast.LENGTH_SHORT).show()
finish()
return
}
// 여기부터는 userId 가 null 아님이 보장
loadUser(userId)
2.3 Kotlin null-safety 연산자 적극 활용
Kotlin 에서는 safe call(?.), Elvis(?:), let 을 적절히 쓰면 NPE 를 대부분 회피할 수 있다. 반대로 !! 는 정말 확신할 때만 써야 하는 “비상용” 연산자다.
val user: User? = repository.getUserOrNull()
// 1) safe call
val nameLength = user?.name?.length
// 2) elvis 연산자
val displayName = user?.name ?: "손님"
// 3) let 사용
user?.let { u ->
textName.text = u.name
}
2.4 비동기 콜백에서 화면 생존 여부 체크
네트워크 응답/코루틴/리스너 콜백에서 UI 를 만지기 전에는, 해당 액티비티나 프래그먼트가 아직 살아있는지 체크하는 습관을 들이면 NPE 를 크게 줄일 수 있다.
// 프래그먼트 예시
api.getUserProfile { user ->
if (!isAdded) return@getUserProfile // Activity 에 붙어있지 않으면 리턴
if (view == null) return@getUserProfile // 뷰가 이미 파괴된 경우
_binding?.let { binding ->
binding.textName.text = user.name
}
}
3. NullPointerException 예방 방법
NPE 는 “발생한 뒤에 잡는 것”보다, 애초에 “발생하지 않게 설계하는 것”이 훨씬 효율적이다. 아래 습관들을 프로젝트 초반부터 적용해두면, 크래시 리포트에서 NPE 를 거의 보지 않게 된다.
3.1 Kotlin nullability 적극 활용 (Non-null 지향)
- 기본은 non-null 타입으로 선언하고, 어쩔 수 없이 null 이 될 수 있는 경우에만
?붙이기 - nullable 타입을 반환하는 함수는 이름/코멘트로 “null 이 나올 수 있다”는 것을 명확히 표현
// Bad
fun findUser(id: String): User? { ... }
// Better - null 가능성을 이름에 드러냄
fun findUserOrNull(id: String): User? { ... }
3.2 !! 연산자는 “마지막 수단”으로만 사용
!! 는 “여기서 null 이 나오면 앱 터져도 상관없다”는 의미와 같다. 테스트 코드나 정말 확실한 부분(예: DI 프레임워크가 주입해주는 필드) 외에는 되도록 사용하지 않는 것이 좋다.
// Bad
val nameLength = user!!.name!!.length
// Good
val nameLength = user?.name?.length ?: 0
3.3 프래그먼트 ViewBinding 템플릿 통일
프로젝트에서 프래그먼트를 새로 만들 때마다, 아래 패턴을 템플릿으로 복붙해서 쓰면 실수를 크게 줄일 수 있다.
class SampleFragment : Fragment() {
private var _binding: FragmentSampleBinding? = null
private val binding get() = _binding
?: throw IllegalStateException("View binding is only valid between onCreateView and onDestroyView")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentSampleBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
3.4 크래시 리포트 도구로 NPE 모니터링
실 서비스에서는 Firebase Crashlytics 같은 크래시 리포트 도구를 반드시 붙여두고, 릴리즈 후에 실제 사용자 환경에서 어떤 NPE 가 발생하는지 모니터링하면서 하나씩 제거해 나가는 것이 좋다.
- 스택 트레이스를 보고 “어떤 경우에 null 이 될 수 있는지”를 케이스 별로 정리
- 재현이 안 되면, 로그/분기별 토스트/이벤트 트래킹을 넣어서 원인 좁히기
마무리
NullPointerException 은 안드로이드에서 가장 흔하게 보는 오류지만, Kotlin 의 null-safety, 올바른 ViewBinding 패턴, Intent/Bundle 방어 코드만 지켜도 대부분 예방할 수 있다.
이 글의 예제 코드를 프로젝트 템플릿 수준으로 가져다 쓰면, 실서비스에서 갑자기 터지는 NPE 크래시를 크게 줄일 수 있다. 앞으로 새로운 화면을 만들 때마다 “이 변수가 null 이 될 수 있을까?”를 한 번만 더 의심해보면, 훨씬 안정적인 앱을 만들 수 있다.
- 프래그먼트에서는 ViewBinding 생명주기 패턴(
_binding+binding게터) 을 통일해서 사용한다. - Intent/Bundle/Arguments 값은 항상 null 가능성을 염두에 두고 기본값/에러 처리를 넣는다.
- Kotlin 의
?.,?:,let을 적극 활용하고,!!는 되도록 쓰지 않는다. - 비동기 콜백에서 UI 를 건드리기 전에 화면/뷰가 살아있는지 먼저 확인한다.
- Firebase Crashlytics 등의 크래시 리포트 도구로 실제 서비스에서 발생하는 NPE 를 꾸준히 모니터링한다.

아래 카카오톡 오픈 채팅 링크
https://open.kakao.com/o/seCteX7h
'개발 > FRONT' 카테고리의 다른 글
| Capacitor 환경에서 Google 광고 AdMob 붙이기 (0) | 2025.12.23 |
|---|---|
| npm cache 때문에 수정이 적용되지 않을 때 해결방법 npmcache 삭제 (0) | 2025.12.23 |
| 휴대폰 본인인증 완벽 구현 가이드 - 아임포트로 끝내는 인증 시스템 예제 (0) | 2025.12.21 |
| Firebase Cloud Messaging(FCM) 완벽 설정 가이드 - 푸시 알림의 모든 것 (0) | 2025.12.21 |
| async/await로 완성하는 비동기 조회 마스터 클래스 - Promise 지옥에서 벗어나기 (0) | 2025.12.21 |