Pureline

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.

RecordsI-prefixed InterfacesModern JavaExplicit Null HandlingVirtual Threads
10Profile rules
18automated
Check Java code

Profile rules

PL-JAVA-001

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.

Avoid
Dumping grounds
src/main/java/
    space/example/project/
        util/
        manager/
        helper/
        misc/
        stuff/
Pureline
src/main/java/
    space/example/project/
        api/
        runtime/
        service/
        network/
        storage/
        model/
PL-JAVA-002 Checker

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.

Avoid
Interface without prefix, cryptic variables
public interface Session {
}

Session s = sessions.open(r);
class sessionService {
}
Pureline
Classes
SessionService
PacketRegistry
UserStorage
ConnectionState
Interfaces
ISession
IStorage
IPacket
IConnection
Methods
open()
close()
load()
save()
execute()
resolve()
PL-JAVA-003 Checker

Prefer Records for Immutable Data

Avoid boilerplate POJOs with fields, constructor, and getters when a record is sufficient.

Avoid
public final class SessionId {
    private final UUID value;

    public SessionId(UUID value) {
        this.value = value;
    }

    public UUID getValue() {
        return value;
    }
}
Pureline
public record SessionId(UUID value) {
}
PL-JAVA-004 Checker

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.

Avoid
Objects.requireNonNull(value);
Pureline
if (value == null) {
    return;
}
Domain-specific failure
if (config == null) {
    throw new ConfigurationException("Missing configuration");
}
PL-JAVA-005 Checker

Use Imports Instead of FQCNs

Fully qualified class names belong in the import block, not in the code itself.

Avoid
java.util.concurrent.CompletableFuture<Result> future;
Pureline
import java.util.concurrent.CompletableFuture;
PL-JAVA-006 Checker

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.

Avoid
if (shape instanceof Circle) {
    Circle circle = (Circle) shape;
    return circle.area();
}
Pureline
if (shape instanceof Circle circle) {
    return circle.area();
}
PL-JAVA-007 Checker

Prefer Switch for Real State Branching

Real state branching is expressed as a switch expression instead of a long chain of unrelated state checks.

Avoid
if (state == State.READY) {
    return start();
}

if (state == State.CLOSED) {
    return stop();
}

if (state == State.FAILED) {
    return recover();
}
Pureline
return switch (state) {
    case READY -> start();
    case CLOSED -> stop();
    case FAILED -> recover();
};
PL-JAVA-008 Checker

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.

Avoid
Blocking I/O in the tick
public void tick() {
    var profile = http.send(request, BodyHandlers.ofString());
    players.update(profile.body());
}
Platform thread per task
new Thread(() -> storage.save(session)).start();
Pureline
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> storage.save(session));
}
PL-JAVA-009

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.

Avoid
Stream with side effects
sessions.stream()
    .filter(Session::expired)
    .peek(session -> log.info("closing {}", session.id()))
    .forEach(session -> {
        session.close();
        storage.delete(session.id());
    });
Pureline
Stream for transformation
var names = users.stream().map(User::name).toList();
Loop for side effects
for (var session : sessions) {
    if (!session.expired()) {
        continue;
    }

    session.close();
    storage.delete(session.id());
}
PL-JAVA-010 Checker

Lifecycle Symmetry

Anything that is opened, registered, started, allocated, or subscribed must have an equally clear corresponding close, unregister, stop, release, or unsubscribe path.

Avoid
No counterpart path
public void start() {
    events.register(listener);
    scheduler.scheduleAtFixedRate(this::tick, 0, 50, MILLISECONDS);
}
Resource without close
var reader = Files.newBufferedReader(path);
return reader.readLine();
Pureline
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: