---
title: "Kotlin과 Android의 java.lang.NullPointerException을 메시지로 해결하기"
description: "Kotlin의 java.lang.NullPointerException은 !!, non-null 타입에 넣은 Java 값, null 캐스트, Java 호출자에서 나와요. JDK 17에서 6가지 방법으로 직접 일으키고 메시지마다 해결법을 보여 드려요."
canonical: "https://logbrew.co/ko/errors/nullpointerexception"
last_updated: "2026-11-10"
---

# Kotlin과 Android의 java.lang.NullPointerException을 메시지로 해결하기

Kotlin의 `java.lang.NullPointerException`은 `!!`, non-null 타입에 넣은 Java 값, `null` 캐스트, Java 호출자에서 나와요. JDK 17에서 6가지 방법으로 직접 일으키고 메시지마다 해결법을 보여 드려요.

Kotlin 2.4.0, OpenJDK 17.0.18 및 logbrew-kotlin 0.2.3 환경에서 2026년 11월 10일에 테스트

글 [Furkan Erday](https://github.com/furkanerday). LogBrew 창업자 Furkan Erday. 매일 Claude Code와 Codex로 LogBrew를 만들어요.

Canonical: https://logbrew.co/ko/errors/nullpointerexception

## `NullPointerException` 메시지가 각각 알려 주는 것

| 코드가 한 일 | `NullPointerException` | 메시지가 원인을 알려 줌 |
| --- | --- | --- |
| null 값에 `order!!` | 네 | 메시지 없음 |
| Java 값을 non-null `val`에 대입 | 네 | `getProperty(...) must not be null` |
| `null as String` | 네 | `null cannot be cast to non-null type` |
| Java에서 Kotlin 매개변수로 `null` 전달 | 네 | `Parameter specified as non-null is null` |
| Java에서 `null`에 메서드 호출 | 네 | `Cannot invoke "String.length()"` |
| 설정 전에 `lateinit` 읽기 | `UninitializedPropertyAccessException` | `lateinit property keg has not been initialized` |

JDK 17과 Kotlin 2.4에서 6번 실행했어요. 첫째 열의 가득 찬 머그잔은 `NullPointerException`이 났다는 뜻이고, 둘째 열은 메시지가 원인을 알려 줬다는 뜻이에요.

## 메시지를 읽고 첫 프레임 보기

### 내 코드에서 그 줄 찾기

내 패키지의 첫 번째 스택 프레임에 파일과 줄이 적혀 있어요. 트레이스에 `Caused by:` 섹션이 있으면 그 아래 프레임부터 읽으세요. 그다음 메시지를 보면 아래 5가지 중 어떤 경우인지 알 수 있어요.

### null 값에 `!!`를 쓰면 메시지 없음

`!!`는 메시지 없는 `NullPointerException`을 던져서, 콜론 뒤에 메시지를 출력하는 로거에는 `java.lang.NullPointerException: null`로 보여요. 첫 프레임의 줄에 `!!`가 있어요.

```bash
val order: String? = null
order!!.length
```

출력

```text
java.lang.NullPointerException
	at NpeKt.main$lambda$0(Npe.kt:27)
```

### Java 값이 non-null 타입에 들어갔을 때의 `must not be null`

Java 메서드는 플랫폼 타입을 반환하고, Kotlin은 그 값을 non-null 변수에 넣을 때만 검사해요. `getProperty(...)`는 `null`을 반환한 호출의 이름이에요.

```bash
val beer: String = System.getProperty("taproom.beer")
```

출력

```text
java.lang.NullPointerException: getProperty(...) must not be null
	at NpeKt.main$lambda$1(Npe.kt:30)
```

### Java가 Kotlin에 `null`을 넘겼을 때의 `Parameter specified as non-null is null`

Kotlin은 public 함수가 호출될 때 non-null 매개변수를 각각 검사해서, Java에서 온 `null`은 함수의 첫 줄에서 실패해요. 메시지에 함수와 매개변수 이름이 나와요.

```bash
// Kotlin
fun greet(name: String): Int = name.length

// Java
NpeKt.greet(null);
```

출력

```text
java.lang.NullPointerException: Parameter specified as non-null is null: method NpeKt.greet, parameter name
	at NpeKt.greet(Npe.kt)
```

### `null`을 캐스트한 뒤의 `null cannot be cast to non-null type`

`null`에 `as String`을 쓰면 예외가 나요. `as? String`은 대신 `null`을 반환하고, 테스트에서도 예외 없이 그렇게 됐어요.

```bash
val value: Any? = null
value as String
```

출력

```text
java.lang.NullPointerException: null cannot be cast to non-null type kotlin.String
	at NpeKt.main$lambda$2(Npe.kt:35)
```

### Java 코드에서 `null`에 메서드를 호출했을 때의 `Cannot invoke`

JDK 15부터 Java는 메서드와 변수 이름을 알려 줘요. `<local0>`은 클래스를 `-g` 없이 컴파일해서 변수 이름이 빠졌다는 뜻이에요. `javac -g`로 컴파일하면 보여요. Android에서는 런타임이 대신 `Attempt to invoke virtual method ... on a null object reference`라고 써요.

```bash
String beer = null;
beer.length();
```

출력

```text
java.lang.NullPointerException: Cannot invoke "String.length()" because "<local0>" is null
	at Caller.lengthOfNull(Caller.java:10)
```

## Kotlin과 Android에서 고치기

### `!!`를 대체값이나 이름 있는 실패로 바꾸기

`?:`는 기본값을 주거나 함수를 빠져나가요. 메시지를 붙인 `requireNotNull()`은 바로 실패하면서 무엇이 빠졌는지 말해 줘서, 첫 프레임과 메시지가 모두 원인을 가리켜요.

```bash
val order = orders[id] ?: return
val keg = requireNotNull(taps[id]) { "keg is not set for tap $id" }
```

출력

```text
java.lang.IllegalArgumentException: keg is not set for tap 3
	at NpeKt.main$lambda$6(Npe.kt:48)
```

### Java 경계에서는 타입을 nullable로 선언하기

Java에서 온 값에 nullable 타입을 주면 컴파일러가 쓰기 전에 `null`을 처리하게 해요. 내 Java 코드에 `@Nullable`과 `@NonNull`을 붙이면 Kotlin이 플랫폼 타입 대신 실제 타입을 봐요.

```bash
val beer: String? = System.getProperty("taproom.beer")
val name = beer ?: "house ale"
```

### JSON 파서는 나중이 아니라 파싱할 때 실패하게 하기

리플렉션으로 객체를 만드는 파서는 non-null Kotlin 프로퍼티에 `null`을 넣을 수 있고, 예외는 파싱과 멀리 떨어진 곳에서 나타나요. square/moshi#143이 2016년에 이 문제를 보고했어요. 2018년 1월에 빠진 non-null 필드를 거부하는 Kotlin 어댑터를 쓰라는 답과 함께 닫혔어요.

### `lateinit`은 처음 쓰기 전에 설정하는 값에만 쓰기

설정 전에 읽은 `lateinit` 프로퍼티는 `NullPointerException`이 아니라 `UninitializedPropertyAccessException`을 던져요. 먼저 `::keg.isInitialized`를 확인하거나, 값을 계산할 수 있으면 `by lazy`를 쓰세요.

```bash
class Taproom {
    lateinit var keg: String
}

Taproom().keg.length
```

출력

```text
kotlin.UninitializedPropertyAccessException: lateinit property keg has not been initialized
	at Taproom.getKeg(Npe.kt:6)
```

### 예외를 스택과 함께 LogBrew로 보내기

Kotlin SDK의 `captureThrowable()`은 예외 타입, 스택 프레임, 원인 체인을 보내고, LogBrew는 반복되는 예외를 이슈 1개로 모아요. `includeMessage = true`를 넘기지 않으면 메시지는 빠져요. 앱에서는 기록용 트랜스포트 대신 `HttpTransport()`를 `flush()`에 넘기세요.

```bash
import co.logbrew.sdk.LogBrewAndroid
import co.logbrew.sdk.LogBrewClient
import co.logbrew.sdk.RecordingTransport

val transport = RecordingTransport.alwaysAccept()
val client = LogBrewClient.create(apiKey = System.getenv("LOGBREW_API_KEY"), sdkName = "logbrew-kotlin", sdkVersion = "0.2.3")
try {
    val beer: String = System.getProperty("taproom.beer")
    beer.length
} catch (e: NullPointerException) {
    LogBrewAndroid.captureThrowable(client, "evt_npe_1", "2026-11-10T09:00:00Z", e)
}
println(client.flush(transport))
```

출력

```text
TransportResponse(statusCode=202, attempts=1)
```

## 직접 테스트해 보니

OpenJDK 17.0.18의 Kotlin 2.4.0으로 Kotlin 파일 1개와 Java 파일 1개를 컴파일하고 6가지 방법으로 예외를 일으켰어요. 4가지는 Kotlin 코드에서 나왔는데, `!!`, non-null `val`에 넣은 Java 값, `null`에 쓴 `as String`, Java가 Kotlin 매개변수로 넘긴 `null`이에요. 다섯 번째는 Java에서 `null`에 메서드를 호출한 경우였어요. 여섯 번째인 `lateinit` 읽기는 아예 `NullPointerException`이 아니었어요.

메시지가 없는 건 `!!` 경우뿐이었어요. 나머지 4개는 호출, 매개변수, 타입, 메서드 중 하나를 이름으로 알려 줬어요. Java 메시지는 `-g` 없이 컴파일해서 변수를 `<local0>`이라고 썼어요.

메시지를 붙인 `requireNotNull()`은 그 코드를 쓴 바로 그 줄에서 `IllegalArgumentException: keg is not set for tap 3`을 던졌어요. `as? String`은 예외 없이 `null`을 반환했어요.

## Kotlin에서도 NullPointerException이 나는 이유

Kotlin 컴파일러는 nullable 타입에 대한 호출을 거부해서, Kotlin 코드는 직접 요청한 곳에서만 `NullPointerException`을 던져요. null 안전성 문서는 `!!`, 명시적인 `throw`, 초기화되지 않은 `this`, Java 상호 운용만 원인으로 꼽아요. 테스트에서 보듯 `as`로 `null`을 캐스트해도 던져요.

Java에서 온 값은 플랫폼 타입이고, 컴파일러 메시지에는 `String!`으로 적혀요. `String`에도 `String?`에도 넣을 수 있어요. `String`에 넣으면 Kotlin이 대입 시점에 검사해서 `must not be null`을 던지는데, 두 번째 테스트가 바로 거기서 실패했어요.

JEP 358이 JDK 14에서 `Cannot invoke ... because ... is null` 메시지를 JVM에 추가했고, JDK 15에서 기본으로 켜졌어요. Android 런타임은 자체 텍스트를 써요. google/gson#1778에 Android 앱에서 나온 `Attempt to invoke virtual method 'int java.lang.Object.hashCode()' on a null object reference`가 있어요.

## LogBrew가 기록하는 것

`captureThrowable()`은 `NullPointerException`이라는 제목의 이슈 1개를 `error` 레벨로 보냈어요. `exception.type`은 `NullPointerException`, 메커니즘은 `android.exception`, 그리고 `handled: true`였어요. 원인 체인에는 `java.lang.NullPointerException` 항목 1개와 스택 프레임 7개가 있었고, 프레임마다 파일, 줄, 함수, 모듈이 담겼어요. 첫 프레임은 `Npe.kt` 58번째 줄의 `main`이었어요.

메시지는 없었고, 체인 항목에는 `messageState: "redacted"`라고 적혀 있었어요. `includeMessage = true`를 주면 이슈 메시지가 `getProperty(...) must not be null`이 됐어요. `includeStackTrace = true`를 주면 메타데이터 필드 `throwableStackTrace`에 메시지 없는 트레이스 텍스트가 담겼어요.

`flush()`는 기록용 트랜스포트를 통해 `statusCode=202, attempts=1`로 응답했어요. `HttpTransport()`를 넘기면 같은 본문이 LogBrew로 가요.

## 자주 묻는 질문

### NullPointerException은 무엇인가요?

코드가 메서드 호출, 필드 읽기, 배열 인덱스, 언박싱에서 `null`을 담은 참조를 쓸 때 JVM이 던지는 예외예요. `RuntimeException`을 상속해서 어떤 `throws` 절에도 적히지 않아요.

### null 안전성이 있는데 Kotlin은 왜 NullPointerException을 던지나요?

Kotlin은 Kotlin 타입에 대해서만 컴파일 시점에 null 가능 여부를 검사해요. `!!`, `null`에 쓴 `as`, non-null 타입에 넣은 Java 값, `null`을 넘기는 Java 호출자는 모두 런타임에 던져요. 각각의 메시지는 위에 정리했어요.

### `java.lang.NullPointerException: null`은 무슨 뜻인가요?

예외에 메시지가 없다는 뜻이에요. 콜론 뒤에 메시지를 출력하는 코드는 거기에 `null`을 써요. Kotlin에서는 `!!`가 정확히 이 예외를 던지니, 첫 프레임의 줄에서 `!!`를 찾으세요.

### Parameter specified as non-null is null은 무슨 뜻인가요?

Java나 리플렉션이 매개변수가 non-null인 Kotlin 함수에 `null`을 넘겼다는 뜻이에요. 메시지에 함수와 매개변수 이름이 나와요. 매개변수를 nullable로 바꾸거나, 호출하는 쪽에서 `null`을 막으세요.

### Android에서 NullPointerException은 어떻게 고치나요?

`Caused by:` 아래에서 내 패키지에 속한 프레임을 읽고, 메시지를 위의 경우들과 맞춰 보세요. Android에서는 런타임이 `Attempt to invoke virtual method ... on a null object reference`라고 쓰는데, 거기 적힌 메서드가 `null`에 대한 호출이에요.

### NullPointerException은 체크 예외인가요?

아니요. `RuntimeException`을 상속해서 컴파일러가 catch를 강제하지 않아요. catch하지 말고 원인을 고치세요. 메서드 호출까지 도달한 `null`은 그 앞 코드의 버그예요.

## 모든 NullPointerException을 스택과 함께 보기

LogBrew Kotlin SDK는 예외마다 타입, 스택 프레임, 원인 체인을 보내고, LogBrew는 반복되는 예외를 이슈 1개로 모아요. 넉넉한 무료 요금제로 시작하세요. 카드 등록은 필요 없어요.

[무료로 시작](https://logbrew.co/ko)

## 출처

- [Kotlin docs, Null safety](https://kotlinlang.org/docs/null-safety.html)
- [Kotlin docs, Null-safety and platform types](https://kotlinlang.org/docs/java-interop.html#null-safety-and-platform-types)
- [JEP 358, Helpful NullPointerExceptions](https://openjdk.org/jeps/358)
- [square/moshi#143](https://github.com/square/moshi/issues/143)
- [google/gson#1778](https://github.com/google/gson/issues/1778)

## 오류 해결법 더 보기

- [메시지와 파일, 줄을 숨기는 스크립트 오류 Script error 해결하기](https://logbrew.co/ko/errors/script-error)
- [Failed to fetch 오류를 고치고 원인 3가지를 구분하는 방법](https://logbrew.co/ko/errors/failed-to-fetch)
- [직접 재현한 오류와 각각의 해결법](https://logbrew.co/ko/errors)
