DEV Community

Cover image for Design Patterns in Java:
Geampiere Jaramillo
Geampiere Jaramillo

Posted on

Design Patterns in Java:

Design patterns are reusable solutions to common software development problems. In Java, mastering these patterns is essential for building maintainable, scalable, and extensible applications.

In this blog, you will learn what design patterns are, their main categories, and practical examples applied in Java.


What Are Design Patterns?

Design patterns are proven solutions that help solve recurring software development problems.

They are not ready-to-copy code, but rather guidelines and structures that help developers write cleaner and more maintainable software.

Main Benefits

  • Reusable code
  • Low coupling
  • Better maintainability
  • Improved organization
  • Scalability
  • Clearer communication between developers

Design Pattern Categories

Design patterns are divided into three major categories:

1. Creational
2. Structural
3. Behavioral
Enter fullscreen mode Exit fullscreen mode

1. Creational Patterns

Creational patterns focus on object creation.

Their goal is to abstract and control how classes are instantiated.


Singleton

The Singleton pattern ensures that only one instance of a class exists.

Java Example

public class DatabaseConnection {

    private static DatabaseConnection instance;

    private DatabaseConnection() {}

    public static DatabaseConnection getInstance() {
        if (instance == null) {
            instance = new DatabaseConnection();
        }
        return instance;
    }
}
Enter fullscreen mode Exit fullscreen mode

Use Cases

  • Global configurations
  • Shared connections
  • Cache systems
  • Logging

Advantages

  • Full control over the instance
  • Reduced memory usage

Disadvantages

  • Can make testing harder
  • Risk of high coupling

Factory Method

The Factory Method pattern delegates object creation to a factory.

Example

interface Notification {
    void send();
}

class EmailNotification implements Notification {
    public void send() {
        System.out.println("Sending email");
    }
}

class SmsNotification implements Notification {
    public void send() {
        System.out.println("Sending SMS");
    }
}

class NotificationFactory {

    public static Notification create(String type) {
        if (type.equals("email")) {
            return new EmailNotification();
        }

        return new SmsNotification();
    }
}
Enter fullscreen mode Exit fullscreen mode

Advantages

  • Reduces coupling
  • Easier extensibility
  • Better maintainability

Use Cases

  • APIs
  • Integrations
  • Systems with multiple implementations

Builder

Builder allows complex objects to be created step by step.

Example

public class User {

    private String name;
    private String email;
    private int age;

    public static class Builder {

        private String name;
        private String email;
        private int age;

        public Builder name(String name) {
            this.name = name;
            return this;
        }

        public Builder email(String email) {
            this.email = email;
            return this;
        }

        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public User build() {
            User user = new User();
            user.name = this.name;
            user.email = this.email;
            user.age = this.age;
            return user;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Advantages

  • More readable objects
  • Avoids massive constructors
  • Easier immutability

Commonly Used In

  • Spring Boot
  • Lombok
  • Modern APIs

2. Structural Patterns

Structural patterns help organize classes and objects into more flexible structures.


Adapter

Adapter allows incompatible interfaces to work together.

Example

interface PaymentProcessor {
    void pay();
}

class PaypalService {
    public void makePayment() {
        System.out.println("Payment with PayPal");
    }
}

class PaypalAdapter implements PaymentProcessor {

    private PaypalService service;

    public PaypalAdapter(PaypalService service) {
        this.service = service;
    }

    @Override
    public void pay() {
        service.makePayment();
    }
}
Enter fullscreen mode Exit fullscreen mode

Use Cases

  • External integrations
  • Legacy APIs
  • Microservices

Decorator

Decorator dynamically adds functionality to an object.

Example

interface Coffee {
    String description();
}

class BasicCoffee implements Coffee {
    public String description() {
        return "Basic coffee";
    }
}

class MilkDecorator implements Coffee {

    private Coffee coffee;

    public MilkDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    public String description() {
        return coffee.description() + " + milk";
    }
}
Enter fullscreen mode Exit fullscreen mode

Advantages

  • Flexible
  • Avoids excessive inheritance
  • Easy extensibility

Facade

Facade provides a simplified interface for complex systems.

Example

class PaymentService {
    void processPayment() {}
}

class NotificationService {
    void sendEmail() {}
}

class OrderFacade {

    private PaymentService payment = new PaymentService();
    private NotificationService notification = new NotificationService();

    public void completeOrder() {
        payment.processPayment();
        notification.sendEmail();
    }
}
Enter fullscreen mode Exit fullscreen mode

Use Cases

  • Complex systems
  • Enterprise APIs
  • Microservices

3. Behavioral Patterns

These patterns focus on communication between objects.


Observer

Observer defines a one-to-many relationship between objects.

When one object changes, the others are automatically notified.

Example

interface Observer {
    void update(String message);
}

class UserObserver implements Observer {

    public void update(String message) {
        System.out.println(message);
    }
}
Enter fullscreen mode Exit fullscreen mode

Use Cases

  • Events
  • Notifications
  • Kafka
  • RabbitMQ

Strategy

Strategy allows algorithms to be changed dynamically.

Example

interface PaymentStrategy {
    void pay();
}

class CreditCardPayment implements PaymentStrategy {
    public void pay() {
        System.out.println("Payment with credit card");
    }
}

class PaypalPayment implements PaymentStrategy {
    public void pay() {
        System.out.println("Payment with PayPal");
    }
}
Enter fullscreen mode Exit fullscreen mode

Advantages

  • Flexible code
  • Easy extensibility
  • Low coupling

Command

Command encapsulates requests as objects.

Example

interface Command {
    void execute();
}

class SaveCommand implements Command {

    public void execute() {
        System.out.println("Saving information");
    }
}
Enter fullscreen mode Exit fullscreen mode

Use Cases

  • Task queues
  • Event systems
  • CQRS

Most Common Patterns in Spring Boot

Spring Boot internally uses many design patterns.

Examples

Pattern Usage in Spring
Singleton Default Beans
Factory BeanFactory
Proxy Spring AOP
Observer Spring Events
Strategy Spring Security
Template Method JdbcTemplate
Dependency Injection Inversion of Control

Best Practices When Using Patterns

1. Avoid Overusing Patterns

Not everything requires a design pattern.

2. Prioritize Simplicity

The best solution is often the simplest one.

3. Apply SOLID Principles

Patterns work best when combined with SOLID principles.

4. Think About Maintainability

Patterns should help the development team.


Difference Between Architecture and Patterns

These concepts are often confused.

Architecture

Defines the overall system structure.

Examples:

  • Microservices
  • Monoliths
  • Hexagonal Architecture
  • Clean Architecture

Patterns

Solve specific problems inside the code.

Examples:

  • Singleton
  • Strategy
  • Factory
  • Observer

Which Patterns Should You Learn First?

If you are starting with Java, focus on:

  1. Singleton
  2. Factory
  3. Builder
  4. Strategy
  5. Observer

These are the most commonly used patterns in real-world applications.


Conclusion

Design patterns are essential tools for every Java developer.

Mastering these patterns helps developers build cleaner, more flexible, and maintainable applications.

The most important thing is not memorizing patterns, but understanding when they truly provide value.

A good developer does not use patterns because they are trendy, but because they solve real problems in an elegant and sustainable way.

Top comments (0)