Package by Responsibility
src/main/java/
space/example/project/
api/
runtime/
service/
network/
storage/
model/ Avoid generic dumping grounds such as util/, manager/, misc/, or helper/ unless the package has a real and narrow purpose.
Naming
SessionService
PacketRegistry
UserStorage
ConnectionState Interfaces use the I prefix:
ISession
IStorage
IPacket
IConnection open()
close()
load()
save()
execute()
resolve() request
session
player
connection Single-letter variables are disallowed unless they are established local conventions such as loop counters.
Prefer Records for Immutable Data
public record SessionId(UUID value) {
} Avoid boilerplate POJOs when a record is sufficient.
Explicit Null Handling
Objects.requireNonNull(value); for normal control-flow validation.
if (value == null) {
return;
} if (config == null) {
throw new ConfigurationException("Missing configuration");
} Use Imports Instead of FQCNs
java.util.concurrent.CompletableFuture<Result> future; import java.util.concurrent.CompletableFuture; Prefer Modern Java
records
sealed types
pattern matching
switch expressions
var
virtual threads
text blocks
streams Modern syntax should reduce noise, not create novelty for its own sake.
Prefer Switch for Real State Branching
return switch (state) {
case READY -> start();
case CLOSED -> stop();
case FAILED -> recover();
}; Prefer this over long chains of unrelated state checks.
Concurrency
Do not block main or tick threads with HTTP, database operations, filesystem access, or blocking network I/O.
Virtual threads are preferred for blocking I/O where appropriate.
Streams Only When Clearer
Streams are encouraged when they improve readability. Use a loop if it is clearer, easier to debug, or better suited to the hot path.
Lifecycle Symmetry
Anything that is opened, registered, started, allocated, or subscribed must have an equally clear corresponding close, unregister, stop, release, or unsubscribe path.