记忆

Java

按配置的 JDK 版本诊断 Java 与 JVM 问题(从 NPE 到容器 OOM),给出可直接套用的代码与配置。

它能做什么

按异常名称分流(NPE、ClassCastException、ConcurrentModificationException、NoClassDefFoundError、NoSuchMethodError、UnsupportedClassVersionError、OutOfMemoryError),并路由到对应子指南,覆盖线程转储、堆转储、容器 RSS 估算(Xmx + metaspace + code cache + 线程数 × Xss + direct + GC)、GC 选型、构建冲突。建议会与用户配置的 jdk_version 对齐,对照版本基线表(record 16、密封类 17、UTF-8 默认字符集 18、虚拟线程 21)。同时覆盖 JUnit 5、Mockito、Testcontainers、Spring 的 @Transactional 与延迟加载、JDBC 连接池调优、HTTP 客户端超时、SLF4J/MDC 接线等。

什么时候用它

  • 解析生产日志中 NPE 或 NoClassDefFoundError 的真实原因
  • 为容器化 JVM 设置 -Xmx 与 GC,避免被 OOM Killer 杀掉
  • 解决 Maven/Gradle 依赖版本冲突与 fat jar shading 丢失 SPI 文件的问题
  • 从 Java 8 或 javax.* 升级到新 LTS 并迁移到 jakarta 命名空间

技能文档

User preferences and memory live in ~/Clawic/data/java/ (see setup.md on first use, memory-template.md for the file format). If you have data at an old location (~/java/ or ~/clawic/java/), move it to ~/Clawic/data/java/.

When To Use

  • Writing or reviewing Java: APIs, data modelling, collections, generics, error handling, concurrency
  • Debugging a running JVM: exceptions, crash loops, deadlocks, 100% CPU, growing heap, slow startup
  • Sizing heap and GC for a container, reading a thread dump or heap dump, profiling and benchmarking
  • Fighting the build: dependency version conflicts, fat jars, module errors, --release mismatches
  • Upgrading a JDK (8 → 17 → 21+), migrating javax → jakarta, replacing removed APIs
  • Testing: JUnit 5, Mockito, Testcontainers, tests that silently don't run or fail only in CI
  • Integrating outward: HTTP clients and timeouts, JDBC pools and batching, logging wiring
  • Not for Kotlin syntax, Android SDK/UI, or JavaScript — different skills; JVM-level advice here still applies under Kotlin

Quick Reference

SituationGo to
An exception or error whose name you must decode; hang, deadlock, 100% CPU, works-in-IDE-onlydebug.md
OutOfMemoryError, heap grows over days, heap dump analysis, container OOM-killmemory.md
Choosing a GC, setting -Xmx in a container, slow startup, JVM flags, -Xlogjvm.md
Slow code and no idea where; JMH benchmark, JFR recording, allocation pressureperformance.md
Shared mutable state, locks, volatile, atomics, deadlock design, virtual threadsconcurrency.md
CompletableFuture chains, executors, timeouts, cancellation, structured concurrencyasync.md
Which collection, the equals/hashCode contract, iteration traps, comparators, mapscollections.md
Stream pipeline wrong or slow, collectors, groupingBy, parallel streamsstreams.md
Lambdas and method references, designing a @FunctionalInterface, capture rules, a checked exception inside a lambdalambdas.md
Designing against null, Optional, autoboxing, nullability annotationsnulls.md
Type erasure, wildcards, List vs List, unchecked warningsgenerics.md
Class design: records, sealed types, pattern matching, immutability, inheritanceclasses.md
Custom annotations, runtime metadata, setAccessible, MethodHandle/VarHandle, dynamic proxies, annotation processorsreflection.md
Checked vs unchecked, try-with-resources, retries, interrupts, logging failuresexceptions.md
Strings, StringBuilder, regex, String.format, charsets, locale-sensitive outputtext.md
Dates, time zones, DST, Instant vs LocalDateTime, formatting patternsdatetime.md
Files, Path, classpath resources, temp files, streams that leak handlesio.md
Jackson/JSON mapping, records in JSON, Java serialization and its CVEsserialization.md
Calling another service: HTTP client choice, timeouts, DNS caching, TLS handshake errorshttp.md
JDBC connections, pool exhaustion, batching, fetch size, driver and transaction behaviorjdbc.md
SLF4J bindings, duplicate or missing log output, MDC, log levels, structured logslogging.md
Maven/Gradle version conflicts, scopes, fat jars, multi-module, reproducible buildsbuild.md
Upgrading a JDK, javax → jakarta, removed APIs, --add-opens, illegal reflective accessmigration.md
Deserialization gadgets, XXE, path traversal, SQL injection, TLS, secrets, crypto choicessecurity.md
JUnit 5, Mockito, AssertJ, Testcontainers, flaky tests, tests that never rantesting.md
Spring Boot: @Transactional, proxies, JPA lazy loading, N+1, bean wiring, config precedencespring.md
Anything elseException Triage and Core Rules below; then reproduce it in a single main with no framework

Core Rules

  1. .equals() for content; == only for primitives, enums, and deliberate identity. Integer a = 128, b = 128; a == b is false, while at 127 it is true — the autobox cache is −128..127 (-XX:AutoBoxCacheMax moves only the upper bound). Use Objects.equals(a, b) whenever either side can be null.
  2. equals and hashCode ship together, over fields that never change. Contract: equal objects must return the same hash; unequal objects may collide. Failure mode: set.add(o), then mutate a field used in hashCode()set.contains(o) is false and the entry is unreachable forever. Hash only final fields (→ collections.md).
  3. Never swallow InterruptedException. catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } — the flag is the only channel that tells the pool to stop; swallowing it makes every shutdownNow() wait out the full timeout and every cancellation silently fail.
  4. Every AutoCloseable in try-with-resources — including Files.lines, Files.walk, JDBC Connection/Statement/ResultSet, and Scanner. Resources close in reverse order, and a failure inside close() arrives as e.getSuppressed() instead of masking the real exception. Leaked handles surface hours later as "Too many open files" (→ io.md).
  5. Size the container, not just the heap. RSS ≈ Xmx + metaspace + code cache + (threads × Xss) + direct buffers + GC structures. Worked: -Xmx512m + ~100 MB metaspace + ~100 MB code cache + 200 threads × 1 MB stack + 64 MB direct ≈ 976 MB — a 1 GiB limit gets OOM-killed at peak with the heap only half full. In containers set -XX:MaxRAMPercentage=75 (the JVM's own default is 25%) and leave the remainder for the non-heap terms (→ jvm.md).
  6. Optional is a return type. orElse(buildDefault()) evaluates its argument on every call even when a value is present; orElseGet(() -> buildDefault()) does not. Never a field, parameter, or collection element — it adds a second empty state and is not serializable (→ nulls.md).
  7. Pin dependency versions in exactly one place. Maven picks the nearest declaration in the tree (ties: first declared, not the highest); Gradle picks the highest version it sees. The same dependency graph therefore yields different jars in the two tools. Declare in `` or a Gradle platform, and verify the winner with mvn dependency:tree -Dverbose (→ build.md).
  8. Compile with --release N, never -source/-target N. -source 8 -target 8 on a JDK 17 compiles happily against JDK 17 APIs and then dies at runtime on Java 8 with NoSuchMethodError; --release 8 also restricts the visible API set. Class-file major version = JDK + 44 (52 = Java 8, 55 = 11, 61 = 17, 65 = 21).
  9. Streams to transform, loops to mutate. Go parallel only when all three hold: per-element work is real, the source splits evenly (arrays, ArrayList, IntStream.range — not LinkedList, Files.lines, Stream.iterate), and nothing shared is mutated. Parallel streams run on ForkJoinPool.commonPool, whose parallelism is availableProcessors() − 1 — in a 1-CPU container that is 0 extra threads, so "parallel" runs entirely on the calling thread (→ streams.md).

Exception Triage

Read the FIRST exception in the log, not the last: the later ones are usually consequences. The getCause() chain matters more than the top frame.

SymptomWhat it really meansFirst move
NullPointerException with a helpful message ("Cannot invoke ... because x.y is null")Helpful NPE messages, on by default since JDK 15Read the message — it names the exact expression that was null
NullPointerException with no stack traceThe JIT recompiled a hot throw site to reuse a preallocated exceptionRestart with -XX:-OmitStackTraceInFastThrow and reproduce
NoClassDefFoundErrorThe class existed at compile time but not at runtime — OR its static initializer threw earlierSearch upward in the log for the first ExceptionInInitializerError; that one carries the real cause
ClassNotFoundExceptionA by-name lookup (reflection, JDBC driver, SPI) failedCheck the runtime classpath, and whether shading dropped META-INF/services (build.md)
NoSuchMethodError / NoSuchFieldErrorVersion skew: compiled against one jar, running against anothermvn dependency:tree -Dverbose -Dincludes= (build.md)
UnsupportedClassVersionError: class file version 65.0Built for a newer JDK than the one running it; major − 44 = JDK (65 → 21)Align --release with the runtime JDK (migration.md)
ClassCastException naming the SAME class on both sidesTwo classloaders loaded it (fat jar plus a container-provided copy)Remove the duplicate; mark the provided one provided/compileOnly
ConcurrentModificationExceptionStructural modification during iteration — single-threaded in most sightings, not a concurrency bugIterator.remove() or removeIf (collections.md)
StackOverflowErrorUnbounded recursion, or two objects whose toString/equals call each otherRead the repeating frame cycle in the trace
OutOfMemoryError (any flavour)Six distinct causes with different fixesmemory.md — the message text selects the chain
IllegalStateException: stream has already been operated upon or closedA stream reused after its terminal operationRebuild the stream from its source (streams.md)
IllegalMonitorStateExceptionwait/notify called without holding that object's monitorconcurrency.md
Process hangs with no exception at allDeadlock, a non-daemon thread that never ends, or a blocked unbounded queueThree thread dumps 10s apart (debug.md)

Version Floors

Check before suggesting an API: it compiles on your JDK and fails on theirs.

FeatureMinimum JDKNote
var for locals10Lambda parameters: 11
HttpClient, single-file source launch11Last LTS where javax.* was still the norm
Text blocks (""")15Incidental trailing whitespace is stripped
Helpful NullPointerException messages15On by default from 15; before that, opt-in
instanceof pattern, records, Stream.toList()16toList() is unmodifiable and null-tolerant; Collectors.toUnmodifiableList() rejects nulls
Sealed classes and interfaces17First LTS enforcing strong encapsulation of JDK internals
UTF-8 as the default charset18JEP 400 — before this the default was platform-dependent
Virtual threads, pattern matching for switch, record patterns, sequenced collections21SequencedCollection.getFirst(), reversed()
synchronized no longer pins a virtual thread's carrier24On 21-23, use ReentrantLock inside virtual threads (concurrency.md)
Structured concurrency (StructuredTaskScope)previewStill a preview API through JDK 25 — requires --enable-preview, and its shape changed between previews

Output Gates

Before emitting Java code or a build change, verify:

  • Every AutoCloseable is inside try-with-resources?
  • equals and hashCode overridden together, computed from final fields only?
  • Charset, Locale, and time zone explicit wherever text, numbers, or time cross a boundary?
  • No raw types, and every @SuppressWarnings("unchecked") justified in a comment?
  • Every caught InterruptedException restores the flag or rethrows?
  • Every API used is at or below the configured jdk_version (→ Version Floors)?
  • New dependency versions declared in one place, not inline per module?
  • No SQL, shell command, or file path built by concatenating input (security.md)?

Configuration

User-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/java/config.yaml.

VariableTypeDefaultEffect
jdk_versionnumber (JDK major, 8-25)from maven.compiler.release, ``, or the Gradle toolchain if present, else 21Gates every API and syntax suggestion against Version Floors; selects flag syntax in jvm.md and the target in migration.md
build_toolmaven | gradle | otherdetected (pom.xml → maven, build.gradle* → gradle), else mavenSelects the resolution rules, commands, and packaging advice in build.md, which covers Maven and Gradle only; other (Bazel, Ant, plain javac) suppresses tool-specific commands and keeps the advice at classpath and jar level
frameworkspring-boot | other | nonedetected from dependencies, else nonespring-boot enables spring.md routing for proxies, @Transactional, and JPA; other (Quarkus, Micronaut, Jakarta EE) keeps guidance at JDK and specification level and states that container-specific DI and transaction semantics are not covered here; none assumes plain Java
localetext (BCP 47 tag, e.g. es-ES)none — Locale.ROOT for machine-facing output, the caller's locale for display; ~/Clawic/profile.yaml is the fallbackFills the explicit Locale argument in formatting, collation, and case-mapping guidance (text.md) and picks the locale used in worked examples
timezonetext (IANA zone id, e.g. Europe/Madrid)none — every example takes an explicit ZoneId, never the system default; ~/Clawic/profile.yaml is the fallbackThe zone assumed when a wall-clock time arrives without one, and the zone shown in datetime.md examples
default_charsettext (charset name)utf-8The charset written into every explicit Charset argument in text.md and io.md; any value other than utf-8 also turns on the legacy-encoding warnings around file.encoding and the JDK 18 default change
lombokboolfalsefalse writes explicit constructors, getters, and equals; true writes Lombok annotations and skips the boilerplate sections of classes.md
nullability_stylejspecify | jakarta | jetbrains | nonenoneWhich @Nullable/@NonNull annotations appear in generated signatures (nulls.md)
preview_featuresboolfalseWhen true, --enable-preview APIs (structured concurrency) become admissible suggestions
test_stackjunit5 | junit4 | testngjunit5Selects assertion and lifecycle idioms in testing.md; junit4 turns on the vintage-engine warnings

Preference areas to record as the user reveals them:

  • tooling — IDE, formatter (google-java-format, palantir, spotless), static analysis (ErrorProne, SpotBugs, NullAway)
  • conventions — package layout, immutability default, builder vs constructor, logging facade and message style, checked-exception policy
  • platform — container vs bare metal, target CPU architecture, GC choice, cloud provider, application server, and the deployment's locale, time zone, and charset when they differ from the locale/timezone/default_charset variables
  • output — depth of explanation (one-line fix vs full diagnosis), whether the reasoning precedes or follows the patch, diff vs whole file, comment density in generated code
  • work order — propose-then-apply vs editing directly, review gate before touching build files or dependency versions, whether to compile and run the tests before handing back, coverage gate
  • cadence — how often to rebuild for CVEs with no code change (build.md), the JDK upgrade window (migration.md), and whether to raise upgrades between windows
  • safety posture — how proactively to flag legacy APIs (Java serialization, SimpleDateFormat, raw types) and to propose dependency or JDK upgrades, vs only on request
  • restrictions — banned APIs or libraries, no-preview-features rule, compliance regime (FIPS crypto, no reflection, offline builds)

Traps

TrapWhy it failsDo instead
log.error(e.getMessage())Drops the stack trace, and the message is null for NPEs and many wrapped exceptionslog.error("context", e) — the throwable is a separate argument
new String(bytes), getBytes(), FileReader, PrintWriter(file)Platform default charset; UTF-8 only became the default in JDK 18, so the same code writes different bytes on an older JVM or on WindowsPass StandardCharsets.UTF_8 explicitly every time
list.remove(someInt) on a ListThe remove(int) overload wins over remove(Object) — it removes by INDEX and can throw IndexOutOfBoundsExceptionlist.remove(Integer.valueOf(x))
SimpleDateFormat in a static or shared fieldNot thread-safe; under load it returns silently wrong dates rather than throwingDateTimeFormatter — immutable and thread-safe (datetime.md)
Collectors.toMap(k, v) on data where a key can repeatThrows IllegalStateException only for inputs that collide, so it passes tests and fails in productionSupply a merge function: toMap(k, v, (a, b) -> b)
@Transactional called from another method of the same classSelf-invocation bypasses the proxy: no transaction, no warning, no rollbackMove the annotated method into another bean (spring.md)
JUnit 4 annotations left in a JUnit 5 projectorg.junit.Test classes are simply not executed by the Jupiter engine — a green build running zero testsOne engine, or add the vintage engine deliberately (testing.md)
Files.lines() / Files.walk() outside try-with-resourcesHolds the file handle until GC; the failure shows up hours later as "Too many open files"try-with-resources (Core Rule 4)
printStackTrace() in server codeWrites to stderr, detached from the request context and invisible to log aggregationLogger with the throwable
catch (Exception e) {} around a retry loopAlso catches InterruptedException and programming errors, turning an outage into silenceCatch the specific exception; rethrow Error and restore interrupts (exceptions.md)
An HTTP or JDBC call with no read timeoutThe default in most Java clients is unlimited: one slow dependency parks every worker thread and the whole service stops answeringSet connect AND read timeouts on every client (http.md, jdbc.md)
Double-checked locking without volatileThe reference can be published before the constructor finishes — another thread sees a half-built objectHolder-class idiom, or volatile on the field (concurrency.md)
Turning on spring.jpa.open-in-view to silence LazyInitializationExceptionHolds a DB connection for the whole request, converting a query bug into pool exhaustion under loadFetch what the view needs in the query (spring.md)

Where Experts Disagree

  • Checked exceptions. Bloch defends them for conditions a caller can actually recover from; most modern frameworks wrap everything unchecked. Working boundary: checked only when the caller has a real alternative path — and never across a lambda or stream, where they do not compose.
  • Optional beyond return types. Its designers scoped it to library return values; a school uses it for fields and parameters anyway. Boundary: return types yes; entity fields, DTOs, and hot loops no (extra allocation, not serializable, two empty states to test).
  • Lombok. Removes real boilerplate vs it is an annotation processor that breaks on JDK upgrades and hides behavior from readers and tools. Since records (16) cover immutable carriers, its honest remaining use is @Slf4j and @Builder on mutable entities.
  • Mocks vs real dependencies in tests. Mock what you own and what is slow or non-deterministic; run repositories and SQL against a real engine (Testcontainers). A mocked JDBC layer verifies the mock, not the query.
  • Virtual threads vs reactive. Since 21, virtual threads deliver most of the throughput of reactive code with straight-line control flow and readable stack traces. Reactive still wins where you genuinely need backpressure across a streaming pipeline — that need, not fashion, is the criterion.

More Clawic skills, get them at https://clawic.com/skills/java (install if the user confirms):

  • kotlin — the other JVM language; same bytecode, different null and concurrency model
  • android — Android SDK, lifecycle, and app packaging
  • sql — the queries your JDBC and JPA code actually sends
  • docker — containerizing a JVM and matching memory limits to heap

Feedback

Part of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/java.

常见问题

能处理构建工具相关的问题吗?
可以。build 指南覆盖 Maven 的『就近胜出』、Gradle 的『最高版胜出』、fat jar 打包丢失 META-INF/services、多模块项目以及可复现构建等场景。
支持哪些 JDK 版本?
从 8 到 25 都覆盖。所有 API 与语法建议都会对照用户配置的 jdk_version,并与版本基线表(record 16、密封类 17、UTF-8 默认 18

相关技能

为 Kotlin 代码提供正确性与惯用法诊断:空安全、协程、Flow、Compose 状态、Java 互操作与构建问题。

84 次安装2 星标

调试、编写和审查 Go 代码,覆盖 goroutine、错误处理、模块与标准库的实践指导。

82 次安装3 星标

围绕运行语义、异步行为与特性版本下限,为 Node 与浏览器场景调试与编写 JavaScript。

作者 Iván135 次安装6 星标

先把 Android 问题归到四层之一——构建、安装、运行时、商店——再对症修复。

115 次安装4 星标

按语言层规则编写、调试、审查 PHP:严格类型、正确转义、合理配置 FPM 与 OPcache。

85 次安装4 星标

针对 Vue 3 响应式、组件、路由和性能问题,依据控制台报错与代码形态给出具体修复方案。

121 次安装8 星标

Iván 的更多技能

浏览全部技能

执行 Git 操作(提交、分支、合并、变基、冲突解决与恢复)时强制套用安全规则。

作者 Iván527 次安装31 星标

用可量化的层级、间距、字号、配色与版式规则,绘制并诊断视觉作品。

作者 Iván137 次安装5 星标

围绕 CSS 机制排查问题并编写组件样式表,而不是凭感觉试错。

作者 Iván97 次安装5 星标

以系统方式规划并执行自学:从出口测试倒推课程,加入间隔复习与刻意练习,产出可验证的迁移证据。

作者 Iván93 次安装3 星标

针对你的 Azure 订阅,做架构设计、故障排查、安全加固与成本优化

作者 Iván86 次安装2 星标

在 EC2、Lambda、RDS、VPC、IAM 等核心服务上做架构、排查、加固与成本优化,每次建议都给出月度开销与故障域。

作者 Iván138 次安装2 星标