|
| 1 | +# Core modern Java practices |
| 2 | + |
| 3 | +Apply these practices at every supported Java version, using only APIs and |
| 4 | +syntax available at the detected compilation target. |
| 5 | + |
| 6 | +## Build and compatibility |
| 7 | + |
| 8 | +- Treat `--release`, Maven `maven.compiler.release`, or Gradle |
| 9 | + `options.release` as the compatibility contract. |
| 10 | +- Pin a reproducible toolchain in the build and CI. Do not rely solely on |
| 11 | + `JAVA_HOME` or a developer's installed JDK. |
| 12 | +- For libraries, test the minimum supported Java release. Avoid accidentally |
| 13 | + linking to APIs from the JDK used to run the build. |
| 14 | +- Keep preview features out of production defaults. If adopted deliberately, |
| 15 | + enable preview consistently for compilation, tests, execution, packaging, |
| 16 | + IDEs, and CI, and plan migration at the next release. |
| 17 | +- Prefer supported LTS releases for long-lived production systems unless the |
| 18 | + organization intentionally follows the six-month release cadence. |
| 19 | + |
| 20 | +## Language and API design |
| 21 | + |
| 22 | +- Model invalid states out of the type system where practical. Use small value |
| 23 | + types, enums, and sealed hierarchies when the target supports them. |
| 24 | +- Prefer immutable state and explicit ownership. Make defensive copies at |
| 25 | + mutable boundaries; do not expose modifiable internal collections. |
| 26 | +- Use `Optional` primarily as a return type for an absent single value, not for |
| 27 | + fields, parameters, collections, or serialization contracts by default. |
| 28 | +- Use exceptions for exceptional failure, with actionable messages and |
| 29 | + preserved causes. Do not catch broad exceptions or silently continue. |
| 30 | +- Prefer standard JDK APIs over dependencies when they are equally clear and |
| 31 | + capable, but do not rewrite mature library functionality merely to remove a |
| 32 | + dependency. |
| 33 | +- Keep public APIs unsurprising. Modern syntax is not a reason to break binary, |
| 34 | + source, serialization, or framework compatibility. |
| 35 | + |
| 36 | +## Collections, streams, and nullness |
| 37 | + |
| 38 | +- Return empty collections instead of `null`. State mutability and encounter |
| 39 | + order in API contracts. |
| 40 | +- Use streams for declarative transformations, not for control flow with hidden |
| 41 | + side effects. Prefer a loop when it is clearer or permits early exit. |
| 42 | +- Do not parallelize streams without measurement and a suitable workload. |
| 43 | +- Establish an explicit nullness policy. Prefer JSpecify annotations when the |
| 44 | + surrounding ecosystem and tooling support them. |
| 45 | + |
| 46 | +## Concurrency |
| 47 | + |
| 48 | +- Prefer structured ownership of tasks and resources. Always define |
| 49 | + cancellation, timeout, interruption, and failure propagation behavior. |
| 50 | +- Preserve interruption (`Thread.currentThread().interrupt()`) when an |
| 51 | + `InterruptedException` cannot be propagated. |
| 52 | +- Use concurrent collections and high-level synchronization utilities before |
| 53 | + hand-written locking. Document invariants protected by locks. |
| 54 | +- Measure before selecting executors, pool sizes, lock-free structures, or |
| 55 | + virtual-thread migration. CPU-bound and I/O-bound workloads need different |
| 56 | + strategies. |
| 57 | + |
| 58 | +## Security and reliability |
| 59 | + |
| 60 | +- Validate untrusted input at boundaries. Avoid native Java serialization for |
| 61 | + untrusted data; apply deserialization filters where legacy use remains. |
| 62 | +- Use modern TLS defaults, authenticated encryption, `SecureRandom`, and |
| 63 | + purpose-built password/key derivation APIs. Never invent cryptography. |
| 64 | +- Use try-with-resources for every `AutoCloseable` whose lifetime is locally |
| 65 | + owned. |
| 66 | +- Add timeouts to network calls and bounded behavior to queues, retries, |
| 67 | + payloads, and caches. |
| 68 | + |
| 69 | +## Performance and observability |
| 70 | + |
| 71 | +- Measure with JFR, JDK tools, and a representative workload before optimizing. |
| 72 | +- Use JMH for microbenchmarks; include warmup, forks, and consumed results. |
| 73 | +- Prefer simple allocation-friendly code, but do not trade correctness or |
| 74 | + clarity for speculative micro-optimizations. |
| 75 | +- Include enough context in logs to diagnose failures without exposing secrets |
| 76 | + or personal data. |
| 77 | + |
| 78 | +## Testing |
| 79 | + |
| 80 | +- Test externally visible behavior and edge cases, not implementation details. |
| 81 | +- Use the project's existing test framework and build. Keep tests deterministic; |
| 82 | + control clocks, randomness, concurrency, locale, timezone, and filesystem |
| 83 | + dependencies where relevant. |
| 84 | +- Add focused regression tests for bug fixes and compatibility tests for public |
| 85 | + APIs or serialized formats. |
0 commit comments