Skip to content
Back to skills

Java Oop Solid Design

ASecurity

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

  • 109 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 19, 2026
ai-agentsgojavasql

Security analysis

A100/100

Scanned October 2, 2026

npx -y skills add makifbaysal/tasktrooper --skill java-oop-solid-design --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Java Oop Solid Design?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Java Oop Solid Design
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/makifbaysal-java-oop-solid-design/badge)](https://www.skillsdirectory.com/skills/makifbaysal-java-oop-solid-design)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: java-oop-solid-design
category: architecture
description: Use when modelling a rich domain in Java - SOLID principles, encapsulated entities that enforce their own invariants, and value objects over primitives
tech_stack: Java
---
# Java OOP & SOLID Design

## Overview

Java is chosen precisely when the domain is rich (see java-vs-go-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

```java
// ❌ 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.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…