The same guide as plain Markdown, built from the same source as the web edition. Rules stay machine-readable: each one starts with its stable ID and level, such as `JAVA-02.1` [MUST], so an agent can cite exactly the rule it means.
Rules digest
~8.5k tokens220 rules, grouped by chapter, no prose or examples. Small enough to keep in an agent's context on every task.
Full guide
~15.9k tokensEvery chapter, table, code sample and drop-in config as one Markdown file. Best when the agent can read files on demand.
Pin a revision
The URLs above always serve the latest text, so an edit to the guide changes what your agent follows. Add @2026.10 after the guide name for a copy that never changes; the snippets below already do. To upgrade, read the changelog for the rules that were added, removed or changed, then bump the revision.
# Revision 2026.10: these files never change curl -fsSL https://style.chopper.dev/java@2026.10/rules.md curl -fsSL https://style.chopper.dev/java@2026.10/llms.txt # What changed between revisions curl -fsSL https://style.chopper.dev/java/changelog.md
Template project
A ready-to-build Maven multi-module repository on Java 23 with the guide's core, api and it modules, the appendix parent POM, jvm.config and .editorconfig, a billing feature with records and JSpecify null-marking, unit and integration tests, an HTTP API on the JDK's server, the Maven wrapper and CI. Both Markdown files above describe it, so an agent asked to start a new Java codebase can fetch a renamed copy instead of scaffolding one by hand.
curl -fsSL "https://style.chopper.dev/java/template/download?name=Project&package=com.example.project" \ -o project.zip && unzip project.zip
| Parameter | Default | Meaning |
|---|---|---|
| name | Project | PascalCase. Names the application class and the Maven artifacts. |
| package | com.example.project | Reverse domain, e.g. com.acme.invoicer. Also the module name. |
Use it with your tools
MCP server / Claude Code plugin
Connect the remote MCP server, or install the plugin, which adds the server plus a java-style skill that loads these rules whenever Claude writes or reviews Java.
claude mcp add --transport http chopper-style https://style.chopper.dev/mcp # or the plugin: MCP server + one skill per guide claude plugin marketplace add carson-hopper/chopper.dev claude plugin install chopper-style@chopper-dev
Claude Code / AGENTS.md
Save the guide into the repo, then point the project instructions file (CLAUDE.md or AGENTS.md) at it.
curl -fsSL https://style.chopper.dev/java@2026.10/llms.txt -o docs/JAVA_STYLE.md # CLAUDE.md / AGENTS.md ## Java style All Java code follows docs/JAVA_STYLE.md (rev. 2026.10). Rules marked [MUST] / [MUST NOT] are non-negotiable; [SHOULD] rules need a comment when broken. Cite rules by ID (JAVA-02.1) in reviews and comments.
Cursor
A project rule that applies the digest whenever a Java file is in play.
mkdir -p .cursor/rules
{ printf -- '---\ndescription: Java Style Guide\nglobs: "**/*.java"\nalwaysApply: false\n---\n\n'
curl -fsSL https://style.chopper.dev/java@2026.10/rules.md; } > .cursor/rules/java-style.mdcAny chat assistant
Paste this ahead of the request, or attach the digest file.
Follow the Java Style Guide at https://style.chopper.dev/java@2026.10/rules.md for all Java code you write or review. Treat MUST / MUST NOT as hard constraints and cite the rule's ID (such as JAVA-02.1) when you flag a violation.
Digest preview
# Java Style Guide: rules digest
> Every rule from the Java Style Guide (rev. 2026.10), without the prose, tables and examples. The full guide: https://style.chopper.dev/java/llms.txt
Revision 2026.10, plus edits not yet released. This URL always serves the latest text; pin a revision with https://style.chopper.dev/java@2026.10/rules.md. Changelog: https://style.chopper.dev/java/changelog.md.
MUST / MUST NOT are non-negotiable. SHOULD / SHOULD NOT are the default; deviate only with a short comment saying why. MAY is allowed when it helps. AVOID is allowed but a smell. Cite a rule by its ID (JAVA-02.1): IDs never change or get reused, and https://style.chopper.dev/java#JAVA-02.1 links to the rule. Code samples use `com.example.project` as the base package and module name, and `Project` as the project name. Both are placeholders; substitute your own. When the guide is silent, match the surrounding code, then the Google Java Style Guide, then Effective Java (3rd edition), in that order.
## Starter project
A ready-to-build Maven multi-module repository on Java 23 with the guide's core, api and it modules, the appendix parent POM, jvm.config and .editorconfig, a billing feature with records and JSpecify null-marking, unit and integration tests, an HTTP API on the JDK's server, the Maven wrapper and CI. It is generated from this guide and follows it, so prefer it over scaffolding a new codebase by hand.
Download it as a zip, renamed to the user's names, with a GET request (query parameters are optional; missing ones take their defaults, invalid ones return HTTP 400 with a message):
```bash
curl -fsSL "https://style.chopper.dev/java/template/download?name=Project&package=com.example.project" -o project.zip && unzip project.zip
```
Parameters:
- `name`: Project name. PascalCase. Names the application class and the Maven artifacts. Must match `^(?:[A-Z][A-Za-z0-9]{1,31})$`. Default `Project`.
- `package`: Base package. Reverse domain, e.g. com.acme.invoicer. Also the module name. Must match `^(?:[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+)$`. Default `com.example.project`.
After unzipping:
1. **Set up.** Install Temurin 23 (pinned in .sdkmanrc), then run ./mvnw spotless:apply once to re-sort imports for your package.
2. **Build.** ./mvnw verify checks formatting, compiles warning-free with Error Prone and NullAway, and runs unit and integration tests.
## 02. Language baseline and toolchain
- `JAVA-02.1` **MUST** compile with `--release 23`, not `-source` / `-target`, so the build cannot accidentally use APIs newer than 23.
- `JAVA-02.2` **MUST** build warning-free with `-Xlint:all -Werror` (individual lints may be turned off in the build file with a comment saying why).
- `JAVA-02.3` **MUST NOT** enable `--enable-preview` in production modules. Preview and incubator features are allowed only in throwaway prototypes and benchmark modules.
- `JAVA-02.4` **MUST** pin the JDK vendor and version in `.java-version` / `.sdkmanrc` and in CI. Use a current Java 23 update build of a mainstream distribution (Temurin, Corretto, Zulu).
- `JAVA-02.5` **SHOULD** plan the move to the next LTS (Java 25) as one tracked task rather than drifting. Java 23 is a non-LTS release with a short public support window.
## 03. Repository and project layout
- `JAVA-03.1` **MUST** follow the standard Maven directory layout (`src/main/java`, `src/test/java`, `src/main/resources`). Every tool, IDE and plugin assumes it.
- `JAVA-03.2` **MUST** put each test class in the same package as the class it tests, under `src/test/java`, named `<Name>Test`: `InvoiceService` is tested by `InvoiceServiceTest`. Same package gives tests package-private access without widening visibility.
- `JAVA-03.3` **SHOULD** organize packages by feature (`billing`, `shipping`), not by layer (`controllers`, `services`, `models`). A feature package holds its types, its service and its repository together.
- `JAVA-03.4` **SHOULD** keep `project-core` free of framework imports (Spring, Jakarta, Micronaut). Adapters depend on core, never the reverse.
- `JAVA-03.5` **MUST** commit the Maven wrapper so a fresh clone builds with `./mvnw verify` and no global install.
- `JAVA-03.6` **AVOID** `util`, `common` or `helpers` packages. A helper belongs to the feature that needs it; promote it to a shared package only when three features need it.
- `JAVA-03.7` **MUST NOT** check in generated sources, `target/` directories or IDE files (`.idea/`, `*.iml`, `.vscode/`).
## 04. Source file structure
- `JAVA-04.1` **MUST** name the file after its single top-level type, case-sensitively: `InvoiceService.java`.
- `JAVA-04.2` **MUST** encode source files in UTF-8 and indent only with ASCII spaces, never tabs.
- `JAVA-04.3` **MUST** keep the `package` and `import` lines unwrapped, whatever their length.
- `JAVA-04.4` **MUST NOT** use wildcard imports (`import java.util.*;`), static or otherwise, or module imports.
- `JAVA-04.5` **MUST** put static imports in one group, then a blank line, then all other imports in a second group, each sorted in ASCII order. google-java-format does this.
- `JAVA-04.6` **MUST** import nested classes with a normal import, not a static one.
- `JAVA-04.7` **SHOULD** use static imports only where they read well and are idiomatic: test assertions, `Collectors.*` methods, and well-known constants. Avoid static imports of ordinary methods from your own code.
- `JAVA-04.8` **MUST** use special escape sequences (`\n`, `\t`, `\"`) rather than octal or Unicode escapes for them.
- `JAVA-04.9` **SHOULD** write non-ASCII characters literally when that is clearer, and as a Unicode escape (with a comment) when the character is invisible or confusable.
## 05. Naming
- `JAVA-05.1` **MUST** treat a field as a constant only if it is `static final` and its contents are deeply immutable. A `static final List<String>` from `ArrayList` is not a constant; a `static final Logger` is not a constant. Neither gets UPPER_SNAKE_CASE.
- `JAVA-05.2` **MUST NOT** style `final` locals or parameters as constants.
- `JAVA-05.3` **MUST** use only ASCII letters and digits in identifiers, with underscores only where the table allows.
- `JAVA-05.4` **SHOULD** name booleans as predicates: `isActive`, `hasLineItems`, `canRetry`.
- `JAVA-05.5` **SHOULD** name methods that return a value without side effects as nouns or `get`/`is` queries, and methods that change state as verbs.
- `JAVA-05.6` **AVOID** one-character parameter names in public methods.
- `JAVA-05.7` **SHOULD** make a name as descriptive as its scope is wide. A public method carries its domain in the name; a counter in a five-line loop can be `i`.
- `JAVA-05.8` **SHOULD NOT** repeat what the context already says. An `Invoice` field is `total`, not `invoiceTotal`.
- `JAVA-05.9` **MUST NOT** abbreviate by deleting letters inside a word: `customerId`, not `cstmrId`.
- `JAVA-05.10` **MUST** write acronyms as one capitalized word: `XmlHttpRequest`, `customerId`, `supportsIpv6`, not `XMLHTTPRequest` or `customerID`.
- `JAVA-05.11` **AVOID** abbreviations a reader outside the project would not know. Universal ones (`id`, `url`, `json`, `i`, `T`) are fine.
## 06. Formatting
- `JAVA-06.1` **SHOULD** break at the highest syntactic level available.
- `JAVA-06.2` **MUST** break before an operator, a `.`, a `::`, the `&` in a type bound, or the `|` in a multi-catch; breaking after `=` is also allowed.
- `JAVA-06.3` **MUST** keep a method or constructor name attached to its `(`, and a comma attached to the token before it.
- `JAVA-06.4` **MUST NOT** break next to a lambda or switch arrow `->`, except right after it when a single unbraced expression follows.
- `JAVA-06.5` **MUST** brace every `if`, `else`, `for`, `do` and `while` body. Lambda bodies may omit braces.
- `JAVA-06.6` **MAY** write an empty block as `{}`, except in a multi-block statement (`if`/`else`, `try`/`catch`/`finally`), where each empty block is written out.
- `JAVA-06.7` **MUST** use arrow (`->`) switches in new code; they never fall through.
- `JAVA-06.8` **MUST** make every switch exhaustive, adding `default` when the compiler does not require it. Switches over sealed types and enums need no `default` when every case is listed.
- `JAVA-06.9` **MAY** put a short arrow case on one line; a braced case body breaks after `{`.
- `JAVA-06.10` **MUST** put class, method and constructor annotations on their own lines after the doc comment. A single annotation with no arguments may share the method's line.
- `JAVA-06.11` **MUST** place type-use annotations (such as `@Nullable`) immediately before the type: `@Nullable String note`.
- `JAVA-06.12` **MAY** put several field annotations on one line.
- `JAVA-06.13` **MUST** put the opening `"""` and the closing `"""` on their own lines, with the closing delimiter at the same indentation as the opening one.
## 07. Modern language features
- `JAVA-07.1` **SHOULD** use a `record` for every immutable data carrier: DTOs, value objects, events, map keys, multi-value returns.
- `JAVA-07.2` **MUST** validate and normalize in a compact constructor; a record never holds an invalid state.
- `JAVA-07.3` **MUST** make defensive copies of mutable components (`List.copyOf`, `Map.copyOf`) in the compact constructor.
- `JAVA-07.4` **AVOID** records for entities with identity or mutable state; use a class.
- `JAVA-07.5` **SHOULD** model a closed set of alternatives as a `sealed interface` with `record` implementations.
- `JAVA-07.6` **SHOULD** handle sealed hierarchies with a `switch` over the type, with no `default`, so adding a variant breaks the build everywhere it is not handled.
- `JAVA-07.7` **SHOULD** use record patterns to deconstruct when it makes the case clearer, and `_` for components you do not use.
- `JAVA-07.8` **MUST** use `instanceof` patterns (`if (shape instanceof Circle c)`) instead of casting after a check.
- `JAVA-07.9` **SHOULD** use `var` when the type is obvious from the right-hand side: a constructor call, a factory method that names its type, or a literal.
- `JAVA-07.10` **SHOULD NOT** use `var` when the type is unclear without an IDE, such as the result of a call to an unfamiliar method.
- `JAVA-07.11` **MUST NOT** use `var` for fields, parameters or return types (the language forbids most of these anyway), or with a diamond: `var list = new ArrayList<>()` infers `ArrayList<Object>`.
- `JAVA-07.12` **SHOULD** use `_` for any variable or pattern component that is required by the syntax but never read: unused catch parameters, lambda parameters, loop variables, record components.
- `JAVA-07.13` **SHOULD** use an `enum` for a fixed set of constants with no data per instance beyond a few fields.
- `JAVA-07.14` **MUST NOT** depend on `ordinal()` for persistence or logic; store and compare by name or by an explicit field.
- `JAVA-07.15` **SHOULD** use `EnumSet` and `EnumMap` instead of hash-based collections keyed by an enum.
## 08. Classes and interfaces
- `JAVA-08.1` **SHOULD** declare classes `final` unless they are designed and documented for inheritance. Prefer composition over inheritance.
- `JAVA-08.2` **SHOULD** default to package-private for classes and members; make something `public` only when another package needs it.
- `JAVA-08.3` **MUST** keep fields `private` (constants excepted). Expose state through methods.
- `JAVA-08.4` **MUST** annotate every override with `@Override`, unless the overridden method is `@Deprecated`.
- `JAVA-08.5` **MUST NOT** override `Object.finalize()`. Use `Cleaner` or try-with-resources.
- `JAVA-08.6` **SHOULD** make fields `final` and assign them once in the constructor.
- `JAVA-08.7` **SHOULD** make value types immutable; use a `record` where it fits (section 7).
- `JAVA-08.8` **MUST** copy mutable inputs on the way in and never hand out mutable internals: return `List.copyOf(items)` or an unmodifiable view.
- `JAVA-08.9` **SHOULD** use static factory methods (`Invoice.draft(...)`, `Money.of(...)`) when a name clarifies intent or the method may return a cached or subtype instance.
- `JAVA-08.10` **SHOULD** use a builder when a type has more than about four optional parameters. Do not build one for two or three.
- `JAVA-08.11` **MUST NOT** call overridable methods from a constructor.
- `JAVA-08.12` **MUST** give a utility class of static methods a `private` constructor and mark it `final`. Before writing one, check whether the methods belong on a type.
- `JAVA-08.13` **MUST** keep overloads (methods or constructors with the same name) together, with nothing in between.
- `JAVA-08.14` **MUST NOT** order members by when they were added. New methods go where they belong, not at the end.
- `JAVA-08.15` **SHOULD** introduce an interface when there are two or more implementations, or when the boundary must be faked in tests. Do not create `FooImpl` for every `Foo`.
- `JAVA-08.16` **SHOULD** name implementations by what makes them different: `PostgresInvoiceRepository`, `InMemoryInvoiceRepository`.
- `JAVA-08.17` **MAY** use `default` methods for behavior that is genuinely the same for every implementation; avoid them as a way to add state-like behavior.
- `JAVA-08.18` **MUST** override `equals` and `hashCode` together, or not at all. Records do this for you.
- `JAVA-08.19` **SHOULD** give every class that appears in logs a `toString` that is useful and never prints secrets.
- `JAVA-08.20` **MUST** qualify static member access with the class name, never with an instance: `Duration.ofSeconds(5)`, not `timeout.ofSeconds(5)`.
## 09. Methods
- `JAVA-09.1` **SHOULD** keep to about 4 parameters. Past that, group related ones into a record: `issue(InvoiceRequest request)`.
- `JAVA-09.2` **AVOID** `boolean` parameters that read as a mystery at the call site (`send(invoice, true)`); use an `enum` or two named methods.
- `JAVA-09.3` **MUST NOT** reassign parameters. Declare a new local instead.
- `JAVA-09.4` **MUST** return an empty collection, never `null`, when there is nothing to return.
- `JAVA-09.5` **SHOULD** return `Optional<T>` from a lookup that may find nothing (section 10). Never return `null` from a method declared to return `Optional`.
- `JAVA-09.6` **SHOULD** prefer guard clauses and early returns over nested `if` blocks.
- `JAVA-09.7` **SHOULD** keep overloads semantically identical; a reader should not need to know which overload ran.
- `JAVA-09.8` **AVOID** varargs on public APIs except where a fixed list of values is the natural call shape (`List.of`, logging).
- `JAVA-09.9` **MUST** declare each local in the narrowest scope possible, as close to its first use as possible, and initialize it where it is declared.
- `JAVA-09.10` **SHOULD** start collections initialized (`List.of(a, b)`) rather than building them with a series of `add` calls when the contents are known.
- `JAVA-09.11` **MAY** mark locals `final`. Neither required nor discouraged; be consistent within a file.
- `JAVA-09.12` **SHOULD** prefer a method reference when it is clearer than the lambda: `.map(Invoice::total)`.
- `JAVA-09.13` **SHOULD** keep lambdas to a few lines. A longer body becomes a named private method referenced by `this::validateLine`.
- `JAVA-09.14` **MUST NOT** mutate captured state from a lambda passed to a stream or another thread; collect a result instead.
## 10. Null handling
- `JAVA-10.1` **MUST** mark every package `@NullMarked` (JSpecify) in its `package-info.java`, making non-null the default for the whole package.
- `JAVA-10.2` **MUST** mark the exceptions with `@Nullable` from `org.jspecify.annotations`, placed directly before the type: `@Nullable String middleName`.
- `JAVA-10.3` **MUST** run NullAway on Error Prone in every build, with JSpecify mode on, failing the build on any null-safety error.
- `JAVA-10.4` **MUST NOT** mix nullness annotation libraries (`javax.annotation`, JetBrains, Checker Framework) in the same codebase. Use JSpecify only.
- `JAVA-10.5` **MUST** check arguments from outside the null-checked code (public API entry points, deserialized data, framework callbacks) with `Objects.requireNonNull(arg, "arg")` at the boundary.
- `JAVA-10.6` **SHOULD** return `Optional<T>` from a method that can legitimately find nothing, such as a repository lookup.
- `JAVA-10.7` **MUST NOT** use `Optional` for fields, method parameters, collection elements or map values. Use `@Nullable` or an empty collection instead.
- `JAVA-10.8` **MUST NOT** call `Optional.get()`. Use `orElseThrow()`, `orElse`, `map` or `ifPresentOrElse`, so the empty case is handled where it is visible.
- `JAVA-10.9` **SHOULD** use the primitive specializations (`OptionalInt`, `OptionalLong`) for primitive results.
- `JAVA-10.10` **MUST** use empty collections and empty strings rather than `null` to mean "none".
- `JAVA-10.11` **MUST NOT** put `null` in a collection. `List.of`, `Set.of` and `Map.of` already reject it.
## 11. Exceptions and errors
- `JAVA-11.1` **SHOULD** throw unchecked exceptions from your own code. Declare a checked exception only when callers are genuinely expected to recover from that specific failure.
- `JAVA-11.2` **MUST NOT** ignore a caught exception. Handle it, log it with context, or rethrow it. If doing nothing is truly correct, name the variable `_` and write a comment saying why.
- `JAVA-11.3` **MUST** keep the cause when translating: `throw new InvoiceStorageException("Saving " + id, e);`.
- `JAVA-11.4` **SHOULD** translate low-level exceptions at the layer boundary into exceptions that make sense to that layer's callers. A billing service does not leak `SQLException`.
- `JAVA-11.5` **MUST** catch the most specific type available. [MUST NOT] catch `Exception`, `Throwable` or `Error` except at a top-level boundary (a request handler, a thread's run loop, `main`) that logs and reports.
- `JAVA-11.6` **MUST** close every `AutoCloseable` with try-with-resources.
- `JAVA-11.7` **MUST NOT** use exceptions for ordinary control flow, or `return` / `throw` from a `finally` block.
- `JAVA-11.8` **MUST** write exception messages that say what failed and with which input, without secrets: `"Invoice INV-1042 has no lines"`, not `"Invalid state"`.
- `JAVA-11.9` **SHOULD** log an exception once, at the place that decides what to do about it, not at every layer it passes through.
- `JAVA-11.10` **SHOULD** use multi-catch for identical handling: `catch (TimeoutException | ConnectException e)`.
## 12. Collections and streams
- `JAVA-12.1` **SHOULD** create fixed collections with `List.of`, `Set.of`, `Map.of` and copy incoming ones with `List.copyOf`, which are immutable and reject nulls.
- `JAVA-12.2` **MUST** declare variables, fields, parameters and return types by interface: `List<Invoice>`, not `ArrayList<Invoice>`.
- `JAVA-12.3` **SHOULD** use the sequenced-collection methods (`getFirst()`, `getLast()`, `reversed()`, `addFirst()`) instead of index arithmetic like `list.get(list.size() - 1)`.
- `JAVA-12.4` **SHOULD** pick the implementation deliberately: `ArrayList` by default, `ArrayDeque` for stacks and queues, `LinkedHashMap` when insertion order matters, `EnumMap` / `EnumSet` for enum keys. [AVOID] `LinkedList`, `Vector`, `Hashtable` and `Stack`.
- `JAVA-12.5` **SHOULD** pre-size collections when the final size is known: `new ArrayList<>(orders.size())`.
- `JAVA-12.6` **MUST NOT** modify a collection while iterating over it; use `removeIf`, an iterator's `remove`, or build a new collection.
- `JAVA-12.7` **SHOULD** put each stream operation on its own line once the pipeline has more than two steps.
- `JAVA-12.8` **MUST NOT** cause side effects inside `map`, `filter` or `peek`. Side effects go in a terminal `forEach`, or better, in a loop.
- `JAVA-12.9` **SHOULD** use `toList()` (unmodifiable) to finish a stream. Use `Collectors.toCollection(...)` only when the result must be mutable.
- `JAVA-12.10` **SHOULD** use a `for` loop when the logic needs `break`, checked exceptions, several accumulators or index access. Forcing those into a stream makes it harder to read.
- `JAVA-12.11` **AVOID** parallel streams unless a benchmark shows a gain on the real workload; they share the common fork-join pool.
- `JAVA-12.12` **MUST NOT** use Stream Gatherers (`Stream.gather`) in production code; they are a preview API in Java 23.
## 13. Concurrency
- `JAVA-13.1` **SHOULD** run I/O-bound tasks (HTTP calls, database queries, file reads) on virtual threads: `Executors.newVirtualThreadPerTaskExecutor()`.
- `JAVA-13.2` **MUST NOT** pool virtual threads. Create one per task; they are cheap.
- `JAVA-13.3` **SHOULD** limit concurrency against a scarce resource with a `Semaphore`, not with a small thread pool.
- `JAVA-13.4` **MUST** guard blocking calls in code that runs on virtual threads with `ReentrantLock` rather than `synchronized`. In Java 23 a virtual thread that blocks inside `synchronized` pins its carrier thread and can starve the scheduler.
- `JAVA-13.5` **AVOID** heavy `ThreadLocal` use with virtual threads; millions of threads each holding a cached object add up. Pass context explicitly.
- `JAVA-13.6` **MUST NOT** use structured concurrency or scoped values in production code; both are preview APIs in Java 23.
- `JAVA-13.7` **MUST** close every `ExecutorService` with try-with-resources (it is `AutoCloseable` and waits for tasks), or shut it down explicitly.
- `JAVA-13.8` **MUST** name long-lived platform threads with a `Thread.ofPlatform().name("invoice-export-", 0)` factory so they show in thread dumps and JFR.
- `JAVA-13.9` **SHOULD** size CPU-bound pools to the number of cores, and keep CPU-bound work off virtual threads.
- `JAVA-13.10` **MUST NOT** call `Thread.stop`, `suspend` or `resume`, or rely on `Thread.sleep` for coordination. Use latches, futures and queues.
- `JAVA-13.11` **MUST** restore the interrupt flag when catching `InterruptedException` and not rethrowing it: `Thread.currentThread().interrupt();`.
- `JAVA-13.12` **SHOULD** share only immutable objects (records, `List.copyOf`) between threads.
- `JAVA-13.13` **MUST** guard every piece of shared mutable state with one documented mechanism: a lock, an atomic, or a concurrent collection.
- `JAVA-13.14` **SHOULD** use `java.util.concurrent` types (`ConcurrentHashMap`, `AtomicLong`, `LongAdder`, `BlockingQueue`) instead of hand-rolled synchronization.
- `JAVA-13.15` **MUST** document thread safety on every class that may be shared: `/// Thread-safe.` or `/// Not thread-safe; confine to one thread.`
- `JAVA-13.16` **AVOID** `volatile` except for a single flag or reference published by one writer.
## 14. Generics and API design
- `JAVA-14.1` **MUST NOT** use raw types (`List`, `Comparable`) except in `instanceof` checks and class literals.
- `JAVA-14.2` **SHOULD** follow PECS for API parameters: `? extends T` for inputs the method reads from (producers), `? super T` for outputs it writes to (consumers).
- `JAVA-14.3` **MUST NOT** use wildcards in return types; they push complexity onto every caller.
- `JAVA-14.4` **MUST** suppress unchecked warnings with `@SuppressWarnings("unchecked")` on the narrowest declaration possible, usually a single local variable, with a comment explaining why the cast is safe.
- `JAVA-14.5` **SHOULD** prefer `List<T>` over `T[]` in APIs; arrays and generics do not mix well.
- `JAVA-14.6` **SHOULD** expose the smallest surface that does the job. Every public type and method is a promise; keep the rest package-private.
- `JAVA-14.7` **MUST** accept and return interfaces and immutable types (`List`, records), never mutable internals.
- `JAVA-14.8` **SHOULD** prefer domain types to primitives once a value crosses a package boundary: `InvoiceId`, `Money`, `Duration` rather than `String`, `BigDecimal`, `long millis`.
- `JAVA-14.9` **MUST** use `java.time` types for all dates and times (`Instant`, `LocalDate`, `Duration`), never `Date`, `Calendar` or `long` timestamps in APIs.
- `JAVA-14.10` **MUST** use `BigDecimal` (or a `Money` type) for money, never `double`.
- `JAVA-14.11` **MUST** inject time and randomness (`Clock`, `RandomGenerator`) instead of calling `Instant.now()` or `new Random()` inside logic, so tests can control them.
- `JAVA-14.12` **SHOULD** mark an API being phased out with `@Deprecated(since = "1.4", forRemoval = true)` plus a doc comment naming the replacement, and remove it in the next major version.
## 15. Modules and packages
- `JAVA-15.1` **SHOULD** give every library module a `module-info.java`. Applications built on frameworks that do not support the module path may skip it, with a note in the README.
- `JAVA-15.2` **MUST** name the Java module after its root package: `module com.example.project.billing`.
- `JAVA-15.3` **MUST** export only packages that other modules are meant to use. Put implementation details in an unexported package such as `com.example.project.billing.internal`.
- `JAVA-15.4` **SHOULD** use `opens` only for packages a framework must reach by reflection (JSON mapping, dependency injection), and prefer `opens ... to <module>` over opening to everyone.
- `JAVA-15.5` **SHOULD** use `requires transitive` only for modules whose types appear in your exported API.
- `JAVA-15.6` **MUST** write `requires` modifiers in the order `transitive`, then `static`.
- `JAVA-15.7` **MUST NOT** create split packages (the same package in two modules).
- `JAVA-15.8` **MUST NOT** use module import declarations (`import module java.base;`); they are a preview feature in Java 23.
- `JAVA-15.9` **SHOULD** mark each package `@NullMarked` in `package-info.java`, alongside its package doc comment (section 10).
## 16. Logging and diagnostics
- `JAVA-16.1` **MUST** use the SLF4J API (`org.slf4j.Logger`) in library code, never a backend directly, `System.out`, `System.err` or `printStackTrace()`.
- `JAVA-16.2` **MUST** declare the logger as `private static final Logger log = LoggerFactory.getLogger(InvoiceService.class);`. It is not a constant, so it is not UPPER_SNAKE_CASE.
- `JAVA-16.3` **MUST** use `{}` placeholders, never string concatenation: `log.info("Issued invoice {} for {}", id, customerId);`.
- `JAVA-16.4` **MUST** pass the exception as the last argument so the stack trace is kept: `log.error("Could not send invoice {}", id, e);`.
- `JAVA-16.5` **MUST NOT** log passwords, tokens, API keys, full card numbers or personal data, at any level.
- `JAVA-16.6` **SHOULD** put request-scoped context (request ID, tenant) in the MDC rather than repeating it in every message, and clear it when the request ends.
- `JAVA-16.7` **SHOULD** log state changes and decisions, not method entry and exit.
- `JAVA-16.8` **SHOULD** emit JSON logs from services so they can be searched by field.
- `JAVA-16.9` **SHOULD** keep Java Flight Recorder available in production (`-XX:StartFlightRecording` with a bounded, rolling recording) and add custom `jdk.jfr.Event`s around key operations.
- `JAVA-16.10` **SHOULD** expose health and metrics through the application framework (Micrometer or equivalent) rather than through logs.
## 17. Testing
- `JAVA-17.1` **MUST** name a test class after the class it tests plus `Test`, in the same package: `com.example.project.billing.InvoiceServiceTest`.
- `JAVA-17.2` **SHOULD** name test methods as behavior: `methodUnderTest_condition_expectedResult` or a plain sentence in lowerCamelCase. Underscores between parts are allowed in test names only.
- `JAVA-17.3` **SHOULD** give each test the shape arrange, act, assert, separated by blank lines.
- `JAVA-17.4` **MUST** keep unit tests in `src/test/java` of the module they test, and tests that start containers, servers or databases in the `project-it` module (run by the Failsafe plugin).
- `JAVA-17.5` **MUST** write assertions with AssertJ (`assertThat(...)`), not JUnit's `assertEquals`, for readable failures.
- `JAVA-17.6` **MUST** add a failing test that reproduces a bug before fixing it.
- `JAVA-17.7` **SHOULD** test through the public (or package-private) API. A test that needs reflection to reach a private member is a sign the class wants splitting.
- `JAVA-17.8` **MUST** make tests deterministic: inject a fixed `Clock`, seed any randomness, and never sleep to wait; poll with Awaitility if you must wait.
- `JAVA-17.9` **SHOULD** use `@ParameterizedTest` for the same behavior over several inputs instead of copying the test.
- `JAVA-17.10` **SHOULD** prefer real objects and simple in-memory fakes over mocks. Use Mockito for awkward collaborators (HTTP clients, clocks you cannot inject), and do not mock types you own just because you can.
- `JAVA-17.11` **MUST NOT** share mutable state between tests; each test sets up what it needs.
- `JAVA-17.12` **SHOULD** keep the unit test suite of a module under about 10 seconds, so it runs on every save.
## 18. Documentation
- `JAVA-18.1` **MUST** write new doc comments with `///` Markdown syntax. Existing `/** */` comments may stay until the file is next edited substantially.
- `JAVA-18.2` **MUST** document every public type and every public or protected member of a public type. Skip it only when there is truly nothing to add (a trivial getter) and for `@Override` methods that keep the parent's contract.
- `JAVA-18.3` **MUST** start with a summary fragment: a noun or verb phrase, capitalized and ending with a period, not a full sentence. Write "Returns the invoice total.", not "This method returns the invoice total."
- `JAVA-18.4` **SHOULD** say what callers need: valid inputs, what is returned when nothing is found, which exceptions are thrown and when, thread safety, and whether the method keeps references to its arguments.
- `JAVA-18.5` **MUST** order block tags `@param`, `@return`, `@throws`, `@deprecated`, and never leave a tag's description empty.
- `JAVA-18.6` **SHOULD** separate paragraphs with a blank `///` line and use Markdown for code (`` `name` ``), lists and links (`[Invoice]`, `[Invoice#total()]`).
- `JAVA-18.7` **MUST** write implementation comments with `//`. Block comments (`/* */`) are allowed for long notes; align their `*` and never draw boxes.
- `JAVA-18.8` **MUST** explain why when the code does something surprising: a workaround, a performance trick, an ordering requirement. Leave the what to the code.
- `JAVA-18.9` **MUST** write TODOs as `// TODO: <issue link> - <what needs doing>`. A TODO that depends on time names a date or event: `// TODO: #318 - remove after the 2027-01 API migration`.
- `JAVA-18.10` **SHOULD** use `// NOTE:` for surprising but correct code and `// HACK:` for knowingly wrong code with a plan to remove it.
- `JAVA-18.11` **AVOID** commented-out code. Git remembers it.
- `JAVA-18.12` **SHOULD** record significant decisions (a framework choice, a persistence model, a concurrency approach) as short ADRs in `docs/adr/NNNN-title.md`.
## 19. Build and static analysis
- `JAVA-19.1` **MUST** set every plugin and dependency version in the parent POM's `<pluginManagement>` and `<dependencyManagement>`, importing BOMs (JUnit, Jackson, Testcontainers) where they exist. Child modules never state versions.
- `JAVA-19.2` **MUST** run `./mvnw spotless:apply` before committing (or install the pre-commit hook); CI only runs `spotless:check`.
- `JAVA-19.3` **MUST** treat warnings as errors in CI. A warning that cannot be fixed is suppressed at the narrowest scope with a comment.
- `JAVA-19.4` **SHOULD** enable reproducible builds by setting `project.build.outputTimestamp`.
- `JAVA-19.5` **SHOULD** let Dependabot or Renovate propose dependency updates weekly, merged as their own commits.
- `JAVA-19.6` **MUST NOT** add a dependency for something the JDK already does well (`java.net.http.HttpClient`, `java.time`, `java.util.concurrent`) without a reason in the commit message.
- `JAVA-19.7` **SHOULD** use Gradle (Kotlin DSL) only where a project already depends on it; the same checks apply.
## 20. Performance
- `JAVA-20.1` **MUST** back any performance claim in a commit or review with a measurement: a JMH benchmark, a JFR recording or a load-test result, with before and after numbers.
- `JAVA-20.2` **SHOULD** write micro-benchmarks with JMH in a separate `project-bench` module, never with `System.nanoTime()` loops in tests.
- `JAVA-20.3` **SHOULD** profile production-like load with JFR and JDK Mission Control before changing code.
- `JAVA-20.4` **SHOULD** avoid autoboxing in hot loops: use `int` over `Integer`, `IntStream` over `Stream<Integer>`, and primitive-specialized collections where a profile shows boxing matters.
- `JAVA-20.5` **SHOULD** build strings in loops with `StringBuilder`; simple concatenation outside loops is fine (the compiler optimizes it).
- `JAVA-20.6` **SHOULD** pre-size `ArrayList`, `HashMap` and `StringBuilder` when the size is known.
- `JAVA-20.7` **MUST** compile a `Pattern` once into a `static final` field rather than calling `String.matches` or `Pattern.compile` per call in hot code.
- `JAVA-20.8` **AVOID** exceptions for control flow on hot paths; filling in stack traces is expensive.
- `JAVA-20.9` **SHOULD** use streaming APIs for large inputs (files, query results) instead of loading everything into memory.
- `JAVA-20.10` **SHOULD** use Generational ZGC (the default ZGC mode in Java 23: `-XX:+UseZGC`) for latency-sensitive services, and the default G1 for batch and throughput work, unless measurement says otherwise.
- `JAVA-20.11` **SHOULD** set an explicit maximum heap (`-Xmx` or `-XX:MaxRAMPercentage` in containers) and record the choice next to the deployment config.
- `JAVA-20.12` **MUST NOT** use `sun.misc.Unsafe` memory-access methods (deprecated for removal in Java 23). Use `VarHandle` or the Foreign Function & Memory API.
## 21. Git and review
- `JAVA-21.1` **MUST** use the feature package or module as the scope (`billing`, `shipping`, `api`).
- `JAVA-21.2` **MUST** keep formatting-only and rename-only changes in their own commits so real diffs stay readable.
- `JAVA-21.3` **SHOULD** mark breaking API changes with `!` and a `BREAKING CHANGE:` footer.