Pureline Java
Modern Java without enterprise ceremony.
Pureline Java uses the modern language directly: records, sealed types, pattern matching, switch expressions, and virtual threads. Framework and infrastructure code stays as close as possible to the actual task.
Profile rules
Package by Responsibility
Packages are cut by responsibility. Avoid generic dumping grounds such as util/, manager/, misc/, or helper/ unless the package has a real and narrow purpose.
src/main/java/
space/example/project/
util/
manager/
helper/
misc/
stuff/ src/main/java/
space/example/project/
api/
runtime/
service/
network/
storage/
model/ Naming
Classes use PascalCase, methods are short verbs, variables name their role. Interfaces use the I prefix.
Single-letter variables are disallowed unless they are established local conventions such as loop counters.
public interface Session {
}
Session s = sessions.open(r);
class sessionService {
} SessionService
PacketRegistry
UserStorage
ConnectionState ISession
IStorage
IPacket
IConnection open()
close()
load()
save()
execute()
resolve() Prefer Records for Immutable Data
Avoid boilerplate POJOs with fields, constructor, and getters when a record is sufficient.
public final class SessionId {
private final UUID value;
public SessionId(UUID value) {
this.value = value;
}
public UUID getValue() {
return value;
}
} public record SessionId(UUID value) {
} Explicit Null Handling
Objects.requireNonNull is not normal control-flow validation. Null states are handled visibly as a guard or answered with a domain-specific exception.
Objects.requireNonNull(value); if (value == null) {
return;
} if (config == null) {
throw new ConfigurationException("Missing configuration");
} Use Imports Instead of FQCNs
Fully qualified class names belong in the import block, not in the code itself.
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, and streams are used when they improve the code.
Modern syntax should reduce noise, not create novelty for its own sake.
if (shape instanceof Circle) {
Circle circle = (Circle) shape;
return circle.area();
} if (shape instanceof Circle circle) {
return circle.area();
} Prefer Switch for Real State Branching
Real state branching is expressed as a switch expression instead of a long chain of unrelated state checks.
if (state == State.READY) {
return start();
}
if (state == State.CLOSED) {
return stop();
}
if (state == State.FAILED) {
return recover();
} return switch (state) {
case READY -> start();
case CLOSED -> stop();
case FAILED -> recover();
}; Concurrency
Never 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.
public void tick() {
var profile = http.send(request, BodyHandlers.ofString());
players.update(profile.body());
} new Thread(() -> storage.save(session)).start(); try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> storage.save(session));
} 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.
sessions.stream()
.filter(Session::expired)
.peek(session -> log.info("closing {}", session.id()))
.forEach(session -> {
session.close();
storage.delete(session.id());
}); var names = users.stream().map(User::name).toList(); for (var session : sessions) {
if (!session.expired()) {
continue;
}
session.close();
storage.delete(session.id());
} Lifecycle Symmetry
Anything that is opened, registered, started, allocated, or subscribed must have an equally clear corresponding close, unregister, stop, release, or unsubscribe path.
public void start() {
events.register(listener);
scheduler.scheduleAtFixedRate(this::tick, 0, 50, MILLISECONDS);
} var reader = Files.newBufferedReader(path);
return reader.readLine(); public void start() {
events.register(listener);
}
public void stop() {
events.unregister(listener);
} try (var reader = Files.newBufferedReader(path)) {
return reader.readLine();
} Core rules in the Java checker
In addition to the profile rules, the Java checker verifies these rules from the Pureline Core: