Skip to content
cccskills
FreePublished on GitHub

java-oop-solid-design

Use when modelling a rich domain in Java - SOLID principles, encapsulated entities that enforce their own invariants, and value objects over primitives

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md4.0 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Java OOP & SOLID Design

Overview

Java is chosen precisely when the domain is rich (see backend-language-decision). That value is only realized if you model it with real objects that protect their invariants — not anemic data bags with a pile of setters and logic scattered in services.

Core principle: Objects own their invariants. If a rule about an entity can be violated from outside the entity, the model is broken.

SOLID, concretely

  • S — Single Responsibility: one reason to change per class. A class that parses HTTP, applies business rules, and writes SQL is three classes.
  • O — Open/Closed: extend behavior via new types/strategies, not by editing a growing switch. New payment method → new PaymentMethod implementation, not another case.
  • L — Liskov: a subtype must honor the supertype's contract. If Square extends Rectangle breaks setWidth, the hierarchy is wrong — prefer composition.
  • I — Interface Segregation: many small role interfaces over one fat one. A caller that needs read shouldn't depend on write.
  • D — Dependency Inversion: services depend on interfaces (ports), not concrete adapters. Inject the interface via the constructor.

Encapsulation over anemic models

// ❌ anemic: invariant lives nowhere, anyone can break it
class Task {
    public String title;      // no bound enforced
    public Status status;
}
task.title = "x".repeat(500); // invalid state, no guard

// ✅ rich: the entity enforces its own rules
public final class Task {
    private final TaskId id;
    private String title;
    private Status status;

    private Task(TaskId id, String title) { this.id = id; this.title = title; }

    public static Task create(String title) {
        if (title == null || title.isBlank() || title.length() > 200)
            throw new InvalidTaskTitle(title);
        return new Task(TaskId.newId(), title);
    }

    public void rename(String title) { /* same guard, reused */ }
    public void complete() {
        if (status == Status.DONE) throw new IllegalTransition("already done");
        this.status = Status.DONE;
    }
    // getters, no public setters
}

Value objects over primitives

Wrap meaningful primitives: TaskId, Email, Money — not raw String/long/BigDecimal. A TaskId can't be accidentally passed where a UserId is expected, and validation lives in one place. Java record makes these cheap.

Prefer

  • record for immutable value objects and DTOs.
  • sealed interfaces + pattern matching for closed hierarchies (states, commands).
  • Immutability by default; expose behavior methods, not setters.
  • Throw domain exceptions (InvalidTaskTitle) mapped to HTTP status at the boundary — not raw IllegalArgumentException leaking out.

Prefer a sealed interface + exhaustive switch pattern matching (final since Java 21) for a closed set of states or commands over a type enum plus if/instanceof chains — the compiler then refuses to let a new case go unhandled.

Common Mistakes

  • Public setters that let callers build invalid state.
  • Business rules in the service that should be on the entity.
  • String/long everywhere instead of value objects.
  • A god-service with dozens of methods and no domain objects.
  • Lombok @Data/@EqualsAndHashCode on a JPA entity — it pulls in lazy associations (forcing a load, or crashing outside the session) and generates a mutable, settable id that breaks equals/hashCode once the entity is persisted. Write equals/hashCode on the id only, or skip Lombok on entities.

Red Flags

  • An entity with only getters/setters and no behavior.
  • The same validation copy-pasted in two services → it belongs on the entity/value object.
  • A switch over a type enum that grows every feature → missing polymorphism.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 18, 2026

See all skills by makifbaysal

Report a problem with this skill