SOTA JVM — Java & Kotlin (2026)
Expert-level rules for producing and auditing production JVM code. The JVM is memory-safe (no buffer overflows/UAF), so the risk shifts to deserialization and injection RCE, concurrency correctness, and dependency supply chain. Baseline: Java 25 LTS (records, sealed types, pattern matching, virtual threads finalized in 21 via JEP 444; scoped values finalized in 25 via JEP 506; structured concurrency is still preview — JEP 505 in 25 — don't present it as final), Kotlin 2.x. Per-language idioms differ; shared concerns (the JMM, the JCA, the build/ supply-chain story) are unified here. Every rule states the why; every rules file ends with an audit checklist of grep/analyzer patterns.
Purpose
Two consumers, one source of truth:
- BUILD mode — generating Java/Kotlin: follow the rules as defaults. Prefer immutability, null-safety, and the standard concurrency primitives. Deviate only with a comment justifying it.
- AUDIT mode — reviewing existing code: hunt violations with the audit checklists, classify by severity, report in the finding format below. Deserialization of untrusted data and string-built queries are presumed exploitable.
BUILD mode
- Before writing, read the rules files relevant to the task (see index). A web
service handling untrusted input + threads + a DB needs
02,03,04. - Apply the top-10 non-negotiables (below) unconditionally.
- New projects: target the current LTS (Java 25), Maven or Gradle with a
lockfile, Error Prone + NullAway (Java) or detekt + ktlint (Kotlin),
SpotBugs/Find-Sec-Bugs, OWASP dependency-check/OSV-Scanner, and CI running
all of it from day one (
rules/06). - Prefer immutability (records,
final, Kotlinval/data class, unmodifiable collections) and the type system (sealed hierarchies, no raw types,Optional/Kotlin nullable types) over runtime checks. - Never let untrusted bytes reach a deserializer, an XML parser with DTDs on,
a JNDI lookup, or a string-built query/EL expression (
rules/04). - When you must use a sharp tool (reflection,
ObjectInputStream,Unsafe, a@SuppressWarnings), leave a// NOTE(sota):comment explaining why and what bounds it.
AUDIT mode
Work each relevant rules file's audit checklist against the target. Run the greps and analyzers (SpotBugs/Find-Sec-Bugs, Error Prone, detekt); confirm hits manually. Check the dependency tree against known-CVE databases.
Severity conventions
| Severity | Meaning | Examples |
|---|---|---|
| CRITICAL | Exploitable on reachable input | ObjectInputStream.readObject on untrusted data, JNDI lookup of attacker URL (Log4Shell), SpEL/OGNL/ScriptEngine eval of input, SQL via string concat, XXE with DTD enabled |
| HIGH | Likely incident or security weakness | Runtime.exec/ProcessBuilder with a shell + interpolation, missing TLS verification, MessageDigest MD5/SHA-1 or Cipher ECB/DES for security, SecureRandom seeded predictably, synchronized+blocking pinning a carrier thread under load (JDK 21–23 only; fixed in 24+ via JEP 491) |
| MEDIUM | Correctness/maintainability hazard | Data race on shared mutable state, equals without hashCode, mutable static state, swallowed exceptions, Kotlin platform-type NPE, resource not in try-with-resources/use |
| LOW | Idiom/perf debt | Mutable collections returned from APIs, raw types, Optional fields/params, needless boxing on hot path, synchronized where j.u.c fits |
| INFO | Style/doc/hygiene | Formatting, naming, missing @Override/@Nullable annotations |
Finding format
[SEVERITY] File.java:LINE — short title
Rule: rules/NN-name.md § section
Evidence: the offending line(s), verbatim
Impact: one sentence — what executes/leaks/races, under what input
Fix: concrete replacement code or action
Effort: trivial | small | medium | large
Group findings by severity, CRITICAL first. End with: counts per severity, the three highest-leverage fixes, and which checklists/analyzers were run.
Rules index
| File | Read this when... |
|---|---|
rules/01-idioms.md |
Writing/reviewing any Java/Kotlin: records, sealed types, pattern matching, switch expressions, text blocks, var; Kotlin null-safety, data/value classes, when, scope functions, immutability, Java↔Kotlin interop, error handling |
rules/02-design-api.md |
Designing types/APIs: nullability discipline (Optional, OptionalInt, @Nullable, Kotlin types, platform types), in-band sentinels (absence encoded as -1/0/"") and the JDK's documented -1, immutability, equals/hashCode/toString, exceptions (checked vs unchecked, Kotlin), AutoCloseable/try-with-resources/use, JPMS/package layout, DI |
rules/03-concurrency.md |
Anything with threads, executors, or shared state: virtual threads and pinning, structured concurrency (preview), ExecutorService, CompletableFuture, the Java Memory Model (volatile/final/happens-before), concurrent collections, Kotlin coroutines/structured concurrency/cancellation |
rules/04-security.md |
Any input crossing a trust boundary: Java deserialization + gadget chains + serialization filters, XXE, JNDI/LDAP (Log4Shell-class), expression-language/ScriptEngine injection, SQL/Runtime.exec, path traversal, JCA crypto (AES-GCM, SecureRandom, no MD5/ECB), TLS, secrets; SEI CERT Oracle Java + OWASP |
rules/05-performance.md |
Latency/throughput/memory work: GC choice (G1 default, Generational ZGC for low pause, Parallel for throughput), heap/-Xmx and container awareness, JIT/tiered/-XX basics, JFR + async-profiler, allocation/escape analysis, GraalVM native image trade-offs, JMH benchmarking |
rules/06-build-tooling-ci.md |
Setting up or auditing build/CI: Maven vs Gradle, dependency locking + supply chain (dependency-check/OSV-Scanner, signing, reproducible builds, SBOM), Error Prone/NullAway, SpotBugs/Find-Sec-Bugs, PMD, ktlint/detekt, spotless, JaCoCo. Test strategy lives in sota-testing; this file owns JVM build/test mechanics (JUnit 5, Testcontainers wiring). |
Top-10 non-negotiables
- Never deserialize untrusted data with Java native serialization.
ObjectInputStream.readObjecton attacker-controlled bytes is RCE via gadget chains. Use JSON/protobuf with a schema; if unavoidable, apply a strictObjectInputFilterallowlist (JEP 290). (rules/04) - No string-built queries or commands. Parameterized JDBC/JPA only;
ProcessBuilderwith an argument list and no shell. String concat into SQL/HQL/JPQL/LDAP/OS commands is CRITICAL. (rules/04) - XML parsers disable DTDs/external entities; no JNDI lookups or EL/script
eval of input. XXE, Log4Shell-class JNDI, and SpEL/OGNL/
ScriptEngineinjection are CRITICAL. (rules/04) - Shared mutable state is correctly synchronized. Respect the JMM: publish
via
volatile/final/j.u.c; prefer immutability and concurrent collections over hand-rolled locking. A data race is a real bug, not a nondeterministic annoyance. (rules/03) - Virtual threads for blocking I/O concurrency (Java 21+); don't pin.
On JDK 21–23
synchronizedaround blocking calls pins the carrier (useReentrantLock); JDK 24+ removes most pinning (JEP 491). Never pool virtual threads. (rules/03) - Resources are closed deterministically — try-with-resources (Java) or
use {}(Kotlin) for everythingAutoCloseable. A leak on the exception path is the default failure of manualfinally. (rules/02) - Crypto uses the JCA correctly. AES-GCM (not ECB/CBC-without-MAC),
SecureRandom(neverRandom/Math.random) for keys/tokens/IVs, no MD5/SHA-1 for security, constant-time compare for secrets. Don't roll your own. (rules/04) - Null is designed, not hoped. Kotlin: lean on non-null types, guard
platform types from Java; Java:
Optionalfor return values (not fields/ params),@Nullable/NullAway to make nullness checked. (rules/01,rules/02) - Exceptions are handled or propagated, never swallowed. No empty
catch; don't catchException/Throwableto hide errors; preserve the cause when wrapping. (rules/01,rules/02) - Dependencies are locked, scanned, and minimal. Lockfile committed,
OWASP dependency-check/OSV-Scanner gates CI, transitive CVEs triaged,
plugins/artifacts from trusted repos. (
rules/06)