---
title: "从消息入手修复 Kotlin 和 Android 中的 java.lang.NullPointerException"
description: "Kotlin 中的 java.lang.NullPointerException 来自 !!、放进非空类型的 Java 值、对 null 的强制转换，或 Java 端的调用。我们在 JDK 17 上用 6 种方式触发它，并给出每条消息的修复方法。"
canonical: "https://logbrew.co/zh-Hans/errors/nullpointerexception"
last_updated: "2026-11-10"
---

# 从消息入手修复 Kotlin 和 Android 中的 java.lang.NullPointerException

Kotlin 中的 `java.lang.NullPointerException` 来自 `!!`、放进非空类型的 Java 值、对 `null` 的强制转换，或 Java 端的调用。我们在 JDK 17 上用 6 种方式触发它，并给出每条消息的修复方法。

2026年11月10日 用 Kotlin 2.4.0、OpenJDK 17.0.18和logbrew-kotlin 0.2.3 实测

作者 [Furkan Erday](https://github.com/furkanerday). Furkan Erday 是 LogBrew 的创始人，每天用 Claude Code 和 Codex 开发 LogBrew。

Canonical: https://logbrew.co/zh-Hans/errors/nullpointerexception

## 每条 `NullPointerException` 消息说明了什么

| 代码做了什么 | `NullPointerException` | 消息说明原因 |
| --- | --- | --- |
| 对 null 值使用 `order!!` | 是 | 无消息 |
| 把 Java 值赋给非空 `val` | 是 | `getProperty(...) must not be null` |
| `null as String` | 是 | `null cannot be cast to non-null type` |
| Java 把 `null` 传给 Kotlin 参数 | 是 | `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 次。第 1 列满杯表示抛出了 `NullPointerException`，第 2 列满杯表示消息说明了原因。

## 先读消息，再看第一帧

### 找到你自己代码里的那一行

你自己包里的第一个堆栈帧写明了文件和行号。如果堆栈里有 `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 值进入非空类型时的 `must not be null`

Java 方法返回平台类型，Kotlin 只在你把它放进非空变量时才检查。`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 把 `null` 传进 Kotlin 时的 `Parameter specified as non-null is null`

Kotlin 在公开函数被调用时检查每个非空参数，所以来自 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 边界把类型声明为可空

给来自 Java 的值一个可空类型，编译器就会要求你在使用前处理 `null`。给你自己的 Java 代码加上 `@Nullable` 和 `@NonNull`，Kotlin 看到的就是真实类型，而不是平台类型。

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

### 让 JSON 解析器在解析时就失败，而不是之后

通过反射构建对象的解析器可能把 `null` 放进非空的 Kotlin 属性，异常会出现在离解析很远的地方。square/moshi#143 在 2016 年报告了这个问题。它在 2018 年 1 月关闭，答复是改用 Kotlin 适配器，它会拒绝缺失的非空字段。

### 只在首次使用前就会赋值的场景用 `lateinit`

在赋值前读取 `lateinit` 属性抛出的是 `UninitializedPropertyAccessException`，不是 `NullPointerException`。先检查 `::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 编译了 1 个 Kotlin 文件和 1 个 Java 文件，用 6 种方式触发异常。4 种来自 Kotlin 代码，分别是 `!!`、放进非空 `val` 的 Java 值、对 `null` 使用 `as String`，以及 Java 传给 Kotlin 参数的 `null`。第 5 种是在 Java 中对 `null` 调用方法。第 6 种，读取 `lateinit`，根本不是 `NullPointerException`。

只有 `!!` 这一种没有消息。其余 4 种分别写明了调用、参数、类型或方法。Java 的消息把变量写成 `<local0>`，因为我们编译时没有加 `-g`。

带消息的 `requireNotNull()` 在我们写它的那一行抛出了 `IllegalArgumentException: keg is not set for tap 3`。`as? String` 返回了 `null`，没有异常。

## Kotlin 为什么仍会抛出 NullPointerException

Kotlin 编译器会拒绝对可空类型的调用，所以 Kotlin 代码只在你主动要求的地方抛出 `NullPointerException`。空安全文档列出的原因只有 `!!`、显式的 `throw`、未初始化的 `this`，以及与 Java 的互操作。正如我们的实测所示，用 `as` 强制转换 `null` 也会抛出。

来自 Java 的值是平台类型，在编译器消息里写作 `String!`。你可以把它放进 `String` 或 `String?`。放进 `String` 时，Kotlin 在赋值处检查并抛出 `must not be null`，我们的第 2 次运行就是在这里失败的。

JEP 358 在 JDK 14 中给 JVM 加入了 `Cannot invoke ... because ... is null` 消息，JDK 15 默认启用。Android 的运行时写的是自己的文本。google/gson#1778 展示了一个 Android 应用里的例子，内容是 `Attempt to invoke virtual method 'int java.lang.Object.hashCode()' on a null object reference`。

## LogBrew 记录了什么

`captureThrowable()` 发送了 1 个标题为 `NullPointerException` 的问题，级别为 `error`，`exception.type` 为 `NullPointerException`，机制为 `android.exception`，并带有 `handled: true`。原因链包含 1 条 `java.lang.NullPointerException` 记录，带 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` 子句里。

### Kotlin 有空安全，为什么还会抛出 NullPointerException？

Kotlin 只在编译期检查 Kotlin 类型的可空性。`!!`、对 `null` 使用 `as`、放进非空类型的 Java 值，以及传入 `null` 的 Java 调用方，都会在运行时抛出。每种都有自己的消息，见上文。

### `java.lang.NullPointerException: null` 是什么意思？

异常没有消息。在冒号后打印消息的代码会在那里写上 `null`。在 Kotlin 中，`!!` 抛出的正是这个异常，所以去第一帧那一行找 `!!`。

### Parameter specified as non-null is null 是什么意思？

Java 或反射把 `null` 传进了参数为非空的 Kotlin 函数。消息写明了函数和参数。把参数改为可空，或者在调用方拦住 `null`。

### 如何在 Android 中修复 NullPointerException？

读取 `Caused by:` 下面属于你的包的那一帧，然后把消息和上面的情况对照。在 Android 上，运行时写的是 `Attempt to invoke virtual method ... on a null object reference`，它写明的方法就是对 `null` 的那次调用。

### NullPointerException 是受检异常吗？

不是。它继承自 `RuntimeException`，所以编译器不会强制你捕获它。不要捕获，去修复原因，因为能到达方法调用的 `null` 是它前面代码里的 bug。

## 看到每个 NullPointerException 及其堆栈

LogBrew Kotlin SDK 发送每个异常的类型、堆栈帧和原因链，LogBrew 把重复出现的异常聚合为 1 个问题。免费额度充足，无需绑卡。

[免费开始](https://logbrew.co/zh-Hans)

## 参考来源

- [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)

## 更多错误修复

- [修复隐藏了消息、文件和行号的脚本错误](https://logbrew.co/zh-Hans/errors/script-error)
- [修复 Failed to fetch 错误，分清它的 3 种原因](https://logbrew.co/zh-Hans/errors/failed-to-fetch)
- [我们复现过的错误，以及每个错误的修复方法](https://logbrew.co/zh-Hans/errors)
