
design-patterns
Common design patterns with Java examples (Factory, Builder, Strategy, Observer, Decorator, etc.). Use when user asks "implement pattern", "use factory", "strategy pattern", or when designing extensible components.
- Installs
- 342
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/decebals/claude-code-java --skill design-patterns- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads design-patterns automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
Design Patterns Skill
Practical design patterns reference for Java with modern examples.
When to Use
- User asks to implement a specific pattern
- Designing extensible/flexible components
- Refactoring rigid code structures
- Code review suggests pattern usage
Quick Reference: When to Use What
| Problem | Pattern |
|---|---|
| Complex object construction | Builder |
| Create objects without specifying class | Factory |
| Multiple algorithms, swap at runtime | Strategy |
| Add behavior without changing class | Decorator |
| Notify multiple objects of changes | Observer |
| Ensure single instance | Singleton |
| Convert incompatible interfaces | Adapter |
| Define algorithm skeleton | Template Method |
Creational Patterns
Builder
Use when: Object has many parameters, some optional.
// ❌ Telescoping constructor antipattern
public class User {
public User(String name) { }
public User(String name, String email) { }
public User(String name, String email, int age) { }
public User(String name, String email, int age, String phone) { }
// ... explosion of constructors
}
// ✅ Builder pattern
public class User {
private final String name; // required
private final String email; // required
private final int age; // optional
private final String phone; // optional
private final String address; // optional
private User(Builder builder) {
this.name = builder.name;
this.email = builder.email;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}
public static Builder builder(String name, String email) {
return new Builder(name, email);
}
public static class Builder {
// Required
private final String name;
private final String email;
// Optional with defaults
private int age = 0;
private String phone = "";
private String address = "";
private Builder(String name, String email) {
this.name = name;
this.email = email;
}
public Builder age(int age) {
this.age = age;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
public User build() {
return new User(this);
}
}
}
// Usage
User user = User.builder("John", "john@example.com")
.age(30)
.phone("+1234567890")
.build();
With Lombok:
@Builder
@Getter
public class User {
private final String name;
private final String email;
@Builder.Default private int age = 0;
private String phone;
}
Factory Method
Use when: Need to create objects without specifying exact class.
// ✅ Factory Method pattern
public interface Notification {
void send(String message);
}
public class EmailNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Email: " + message);
}
}
public class SmsNotification implements Notification {
@Override
public void send(String message) {
System.out.println("SMS: " + message);
}
}
public class PushNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Push: " + message);
}
}
// Factory
public class NotificationFactory {
public static Notification create(String type) {
return switch (type.toUpperCase()) {
case "EMAIL" -> new EmailNotification();
case "SMS" -> new SmsNotification();
case "PUSH" -> new PushNotification();
default -> throw new IllegalArgumentException("Unknown type: " + type);
};
}
}
// Usage
Notification notification = NotificationFactory.create("EMAIL");
notification.send("Hello!");
With Spring (preferred):
public interface NotificationSender {
void send(String message);
String getType();
}
@Component
public class EmailSender implements NotificationSender {
@Override public void send(String message) { /* ... */ }
@Override public String getType() { return "EMAIL"; }
}
@Component
public class SmsSender implements NotificationSender {
@Override public void send(String message) { /* ... */ }
@Override public String getType() { return "SMS"; }
}
@Component
public class NotificationFactory {
private final Map<String, NotificationSender> senders;
public NotificationFactory(List<NotificationSender> senderList) {
this.senders = senderList.stream()
.collect(Collectors.toMap(
NotificationSender::getType,
Function.identity()
));
}
public NotificationSender getSender(String type) {
return Optional.ofNullable(senders.get(type))
.orElseThrow(() -> new IllegalArgumentException("Unknown: " + type));
}
}
Singleton
Use when: Exactly one instance needed (use sparingly!).
// ✅ Modern singleton (enum-based, thread-safe)
public enum DatabaseConnection {
INSTANCE;
private Connection connection;
DatabaseConnection() {
// Initialize connection
}
public Connection getConnection() {
return connection;
}
}
// Usage
Connection conn = DatabaseConnection.INSTANCE.getConnection();
With Spring (preferred):
@Component // Default scope is singleton
public class DatabaseConnection {
// Spring manages single instance
}
Warning: Singletons can be problematic:
- Hard to test (global state)
- Hidden dependencies
- Consider dependency injection instead
Behavioral Patterns
Strategy
Use when: Multiple algorithms for same operation, need to swap at runtime.
// ✅ Strategy pattern
public interface PaymentStrategy {
void pay(BigDecimal amount);
}
public class CreditCardPayment implements PaymentStrategy {
private final String cardNumber;
public CreditCardPayment(String cardNumber) {
this.cardNumber = cardNumber;
}
@Override
public void pay(BigDecimal amount) {
System.out.println("Paid " + amount + " with card " + cardNumber);
}
}
public class PayPalPayment implements PaymentStrategy {
private final String email;
public PayPalPayment(String email) {
this.email = email;
}
@Override
public void pay(BigDecimal amount) {
System.out.println("Paid " + amount + " via PayPal: " + email);
}
}
public class CryptoPayment implements PaymentStrategy {
private final String walletAddress;
public CryptoPayment(String walletAddress) {
this.walletAddress = walletAddress;
}
@Override
public void pay(BigDecimal amount) {
System.out.println("Paid " + amount + " to wallet: " + walletAddress);
}
}
// Context
public class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
public void checkout(BigDecimal total) {
paymentStrategy.pay(total);
}
}
// Usage
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CreditCardPayment("4111-1111-1111-1111"));
cart.checkout(new BigDecimal("99.99"));
// Change strategy at runtime
cart.setPaymentStrategy(new PayPalPayment("user@example.com"));
cart.checkout(new BigDecimal("49.99"));
With Java 8+ (functional):
// Strategy as functional interface
@FunctionalInterface
public interface PaymentStrategy {
void pay(BigDecimal amount);
}
// Usage with lambdas
PaymentStrategy creditCard = amount ->
System.out.println("Card payment: " + amount);
PaymentStrategy paypal = amount ->
System.out.println("PayPal payment: " + amount);
cart.setPaymentStrategy(creditCard);
Observer
Use when: Objects need to be notified of changes in another object.
// ✅ Observer pattern (modern Java)
public interface OrderObserver {
void onOrderPlaced(Order order);
}
public class OrderService {
private final List<OrderObserver> observers = new ArrayList<>();
public void addObserver(OrderObserver observer) {
observers.add(observer);
}
public void removeObserver(OrderObserver observer) {
observers.remove(observer);
}
public void placeOrder(Order order) {
// Process order
saveOrder(order);
// Notify all observers
observers.forEach(observer -> observer.onOrderPlaced(order));
}
}
// Observers
public class InventoryService implements OrderObserver {
@Override
public void onOrderPlaced(Order order) {
// Reduce inventory
order.getItems().forEach(item ->
reduceStock(item.getProductId(), item.getQuantity())
);
}
}
public class EmailNotificationService implements OrderObserver {
@Override
public void onOrderPlaced(Order order) {
sendConfirmationEmail(order.getCustomerEmail(), order);
}
}
public class AnalyticsService implements OrderObserver {
@Override
public void onOrderPlaced(Order order) {
trackOrderEvent(order);
}
}
// Setup
OrderService orderService = new OrderService();
orderService.addObserver(new InventoryService());
orderService.addObserver(new EmailNotificationService());
orderService.addObserver(new AnalyticsService());
With Spring Events (preferred):
// Event
public record OrderPlacedEvent(Order order) {}
// Publisher
@Service
public class OrderService {
private final ApplicationEventPublisher eventPublisher;
public void placeOrder(Order order) {
saveOrder(order);
eventPublisher.publishEvent(new OrderPlacedEvent(order));
}
}
// Listeners (observers)
@Component
public class InventoryListener {
@EventListener
public void handleOrderPlaced(OrderPlacedEvent event) {
// Reduce inventory
}
}
@Component
public class EmailListener {
@EventListener
public void handleOrderPlaced(OrderPlacedEvent event) {
// Send email
}
@EventListener
@Async // Async processing
public void handleOrderPlacedAsync(OrderPlacedEvent event) {
// Send email asynchronously
}
}
Template Method
Use when: Define algorithm skeleton, let subclasses fill in steps.
// ✅ Template Method pattern
public abstract class DataProcessor {
// Template method - defines the algorithm
public final void process() {
readData();
processData();
writeData();
if (shouldNotify()) {
notifyCompletion();
}
}
// Steps to be implemented by subclasses
protected abstract void readData();
protected abstract void processData();
protected abstract void writeData();
// Hook - optional override
protected boolean shouldNotify() {
return true;
}
protected void notifyCompletion() {
System.out.println("Processing completed!");
}
}
public class CsvDataProcessor extends DataProcessor {
@Override
protected void readData() {
System.out.println("Reading CSV file...");
}
@Override
protected void processData() {
System.out.println("Processing CSV data...");
}
@Override
protected void writeData() {
System.out.println("Writing to database...");
}
}
public class ApiDataProcessor extends DataProcessor {
@Override
protected void readData() {
System.out.println("Fetching from API...");
}
@Override
protected void processData() {
System.out.println("Transforming API response...");
}
@Override
protected void writeData() {
System.out.println("Writing to cache...");
}
@Override
protected boolean shouldNotify() {
return false; // Override hook
}
}
// Usage
DataProcessor csvProcessor = new CsvDataProcessor();
csvProcessor.process();
DataProcessor apiProcessor = new ApiDataProcessor();
apiProcessor.process();
Structural Patterns
Decorator
Use when: Add behavior dynamically without modifying existing classes.
// ✅ Decorator pattern
public interface Coffee {
String getDescription();
BigDecimal getCost();
}
public class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Coffee";
}
@Override
public BigDecimal getCost() {
return new BigDecimal("2.00");
}
}
// Base decorator
public abstract class CoffeeDecorator implements Coffee {
protected final Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription();
}
@Override
public BigDecimal getCost() {
return coffee.getCost();
}
}
// Concrete decorators
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
@Override
public BigDecimal getCost() {
return coffee.getCost().add(new BigDecimal("0.50"));
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Sugar";
}
@Override
public BigDecimal getCost() {
return coffee.getCost().add(new BigDecimal("0.20"));
}
}
public class WhippedCreamDecorator extends CoffeeDecorator {
public WhippedCreamDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Whipped Cream";
}
@Override
public BigDecimal getCost() {
return coffee.getCost().add(new BigDecimal("0.70"));
}
}
// Usage - compose decorators
Coffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
coffee = new WhippedCreamDecorator(coffee);
System.out.println(coffee.getDescription()); // Coffee, Milk, Sugar, Whipped Cream
System.out.println(coffee.getCost()); // 3.40
Java I/O uses Decorator:
// Classic example from Java
BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("file.txt")
)
);
Adapter
Use when: Make incompatible interfaces work together.
// ✅ Adapter pattern
// Existing interface our code uses
public interface MediaPlayer {
void play(String filename);
}
// Legacy/third-party interface
public class LegacyAudioPlayer {
public void playMp3(String filename) {
System.out.println("Playing MP3: " + filename);
}
}
public class AdvancedVideoPlayer {
public void playMp4(String filename) {
System.out.println("Playing MP4: " + filename);
}
public void playAvi(String filename) {
System.out.println("Playing AVI: " + filename);
}
}
// Adapters
public class Mp3PlayerAdapter implements MediaPlayer {
private final LegacyAudioPlayer legacyPlayer = new LegacyAudioPlayer();
@Override
public void play(String filename) {
legacyPlayer.playMp3(filename);
}
}
public class VideoPlayerAdapter implements MediaPlayer {
private final AdvancedVideoPlayer videoPlayer = new AdvancedVideoPlayer();
@Override
public void play(String filename) {
if (filename.endsWith(".mp4")) {
videoPlayer.playMp4(filename);
} else if (filename.endsWith(".avi")) {
videoPlayer.playAvi(filename);
}
}
}
// Usage
MediaPlayer mp3Player = new Mp3PlayerAdapter();
mp3Player.play("song.mp3");
MediaPlayer videoPlayer = new VideoPlayerAdapter();
videoPlayer.play("movie.mp4");
Pattern Selection Guide
| Situation | Consider |
|---|---|
| Object creation is complex | Builder, Factory |
| Need to add features dynamically | Decorator |
| Multiple implementations of algorithm | Strategy |
| React to state changes | Observer |
| Integrate with legacy code | Adapter |
| Common algorithm, varying steps | Template Method |
| Need single instance | Singleton (use sparingly) |
Anti-Patterns to Avoid
| Anti-Pattern | Problem | Better Approach |
|---|---|---|
| Singleton abuse | Global state, hard to test | Dependency Injection |
| Factory everywhere | Over-engineering | Simple new if type is known |
| Deep decorator chains | Hard to debug | Keep chains short, consider composition |
| Observer with many events | Spaghetti notifications | Event bus, clear event hierarchy |
Related Skills
solid-principles- Design principles that patterns help implementclean-code- Code-level best practicesspring-boot-patterns- Spring-specific implementations
FAQ
What does the design-patterns skill do?
Common design patterns with Java examples (Factory, Builder, Strategy, Observer, Decorator, etc.). Use when user asks "implement pattern", "use factory", "strategy pattern", or when designing extensible components.
How do I install design-patterns?
Run npx skills add https://github.com/decebals/claude-code-java --skill design-patterns in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/design-patterns/ (personal) or .claude/skills/design-patterns/ (project).
Which AI agents can use design-patterns?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is design-patterns safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains design-patterns?
It is published by decebals in decebals/claude-code-java. It has 342 recorded installs.
More from decebals/claude-code-java
java-code-review
decebals/claude-code-java
Systematic code review for Java with null safety, exception handling, concurrency, and performance checks. Use when user says "review code", "check this PR", "code review", or before merging changes.
clean-code
decebals/claude-code-java
Clean Code principles (DRY, KISS, YAGNI), naming, function design and readability. Use when code is hard to read, with long methods, unclear names, duplication or deep nesting. For how responsibilities are split across classes and which way dependencies point, use solid-principles instead.
concurrency-review
decebals/claude-code-java
Review Java concurrency code for thread safety, race conditions, deadlocks, and modern patterns (Virtual Threads, CompletableFuture, @Async). Use when user asks "check thread safety", "concurrency review", "async code review", or when reviewing multi-threaded code.
solid-principles
decebals/claude-code-java
SOLID principles checklist with Java examples. Use when a class has too many responsibilities, an abstraction leaks, or a dependency points the wrong way, and when the user asks about Single Responsibility, Open/Closed, Liskov, Interface Segregation or Dependency Inversion. For naming, duplication and method length, use clean-code instead.
architecture-review
decebals/claude-code-java
Analyze Java project architecture at macro level - package structure, module boundaries, dependency direction, and layering. Use when user asks "review architecture", "check structure", "package organization", or when evaluating if a codebase follows clean architecture principles.
spring-boot-patterns
decebals/claude-code-java
Spring Boot best practices and patterns. Use when creating controllers, services or repositories, or when the user asks about Spring Boot layering, wiring, configuration or exception handling. For JPA and Hibernate behaviour, use jpa-patterns instead.
Popular backend & apis skills
find-skills
vercel-labs/skills
Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
entra-app-registration
microsoft/azure-skills
Guides Microsoft Entra ID app registration, OAuth 2.0 authentication, and MSAL integration. USE FOR: create app registration, register Azure AD app, configure OAuth, set up authentication, add API permissions, generate service principal, MSAL example, console app auth, Entra ID setup, Azure AD authentication. DO NOT USE FOR: Key Vault secrets (use azure-keyvault-expiration-audit), general Azure resource security guidance.
google-agents-cli-adk-code
google/agents-cli
This skill should be used when the user wants to "write agent code", "build an agent with ADK", "add a tool", "create a callback", "define an agent", "use state management" — in a project that needs ADK (Agent Development Kit) API patterns and code examples. It provides a quick reference for agent types, tool definitions, orchestration patterns, callbacks, state management, the graph Workflow API, and reference recipes to study. Do NOT use for scaffolding (use google-agents-cli-scaffold) or deployment (use google-agents-cli-deploy).
embedded-captions
heygen-com/hyperframes
Add captions or subtitles to an existing single-subject talking-head video without editing the footage. Use for plain verbatim captions, cinematic captions embedded behind the subject, VFX captions, “炸/特效/酷炫字幕,” or a named identity from the 35-style catalog. Route by visual identity, not by backend engine. The quiet anchor rail is the default; embed every word only when the user explicitly wants a fully cinematic treatment. The workflow runs locally end to end, including transcription and subject matting; split multi-shot footage before applying it.
entra-agent-id
microsoft/azure-skills
Provision Microsoft Entra Agent Identity Blueprints, BlueprintPrincipals, and per-instance Agent Identities via Microsoft Graph, and configure OAuth 2.0 token exchange (fmipath, OBO, cross-tenant) including the Microsoft Entra SDK for AgentID sidecar. USE FOR: Agent Identity Blueprint, BlueprintPrincipal, agent OAuth, fmipath token exchange, agent OBO, Workload Identity Federation for agents, polyglot agent auth, Microsoft.Identity.Web.AgentIdentities. DO NOT USE FOR: standard Entra app registration (use entra-app-registration), Microsoft Foundry agent authoring (use microsoft-foundry).
neon
neondatabase/agent-skills
Overview of Neon, a complete set of cloud backend primitives around Lakebase Postgres: Auth, Object Storage, Functions, the AI Gateway, and Realtime. Start here to choose Neon for undecided login, files, APIs, and LLM calls, set up the CLI or MCP server, and follow the branch-first workflow. Use when building an app or backend on Neon, or when "Neon" or "Lakebase Postgres" is mentioned. Child skill neon-postgres wins for an existing DATABASEURL, SQL, schema, inspect, or search. Child skill neon-auth wins for login, users, sessions, identity routing, and Managed Better Auth setup. Child skill neon-realtime wins for live queries in reactive apps and agents. Also use for object storage, S3, buckets, serverless functions, function triggers, cron, AI gateway, LLM calls, realtime, logs, Loki, Grafana, observability, postgres, database, backend, Claimable Neon, neon.new, or a no-signup database.