Tutorial

Most Common Design Patterns in Java (with Examples)

Most Common Design Patterns in Java (with Examples)

Introduction

Design patterns are very popular among software developers. A design pattern is a well-described solution to a common software problem.

Some of the benefits of using design patterns are:

  1. Design patterns are already defined and provide an industry-standard approach to solving a recurring problem, so it saves time if we sensibly use the design pattern. There are many Java design patterns that we can use in our Java-based projects.
  2. Using design patterns promotes reusability that leads to more robust and highly maintainable code. It helps in reducing the total cost of ownership (TCO) of the software product.
  3. Since design patterns are already defined, it makes our code easy to understand and debug. It leads to faster development and new members of the team understand it easily.

Java design patterns are divided into three categories - creational, structural, and behavioral design patterns.

This article serves as an index for all the Java design pattern articles.

Creational Design Patterns

Creational design patterns provide solutions to instantiate an Object in the best possible way for specific situations.

1. Singleton Pattern

The singleton pattern restricts the instantiation of a Class and ensures that only one instance of the class exists in the Java Virtual Machine. The implementation of the singleton pattern has always been a controversial topic among developers.

Note: Learn more about the Singleton Design Pattern.

2. Factory Pattern

The factory design pattern is used when we have a superclass with multiple subclasses and based on input, we need to return one of the subclasses. This pattern takes out the responsibility of the instantiation of a Class from the client program to the factory class. We can apply a singleton pattern on the factory class or make the factory method static.

Note: Learn more about the Factory Design Pattern.

3. Abstract Factory Pattern

The abstract factory pattern is similar to the factory pattern and is a factory of factories. If you are familiar with the factory design pattern in Java, you will notice that we have a single factory class that returns the different subclasses based on the input provided and the factory class uses if-else or switch statements to achieve this. In the abstract factory pattern, we get rid of if-else block and have a factory class for each subclass and then an abstract factory class that will return the subclass based on the input factory class.

Note: Learn more about the Abstract Factory Pattern.

4. Builder Pattern

The builder pattern was introduced to solve some of the problems with factory and abstract Factory design patterns when the object contains a lot of attributes. This pattern solves the issue with a large number of optional parameters and inconsistent state by providing a way to build the object step-by-step and provide a method that will actually return the final Object.

Note: Learn more about the Builder Pattern.

5. Prototype Pattern

The prototype pattern is used when the Object creation is costly and requires a lot of time and resources, and you have a similar Object already existing. So this pattern provides a mechanism to copy the original Object to a new Object and then modify it according to our needs. This pattern uses Java cloning to copy the Object. The prototype design pattern mandates that the Object which you are copying should provide the copying feature. It should not be done by any other class. However, whether to use the shallow or deep copy of the object properties depends on the requirements and is a design decision.

Note: Learn more about the Prototype Pattern.

Structural Design Patterns

Structural design patterns provide different ways to create a Class structure (for example, using inheritance and composition to create a large Object from small Objects).

1. Adapter Pattern

The adapter design pattern is one of the structural design patterns and is used so that two unrelated interfaces can work together. The object that joins these unrelated interfaces is called an adapter.

Note: Learn more about the Adapter Pattern.

2. Composite Pattern

The composite pattern is used when we have to represent a part-whole hierarchy. When we need to create a structure in a way that the objects in the structure have to be treated the same way, we can apply the composite design pattern.

Note: Learn more about the Composite Pattern.

3. Proxy Pattern

The proxy pattern provides a placeholder for another Object to control access to it. This pattern is used when we want to provide controlled access to functionality.

Note: Learn more about the Proxy Pattern.

4. Flyweight Pattern

The flyweight design pattern is used when we need to create a lot of Objects of a Class. Since every Object consumes memory space that can be crucial for low-memory devices (such as mobile devices or embedded systems), the flyweight design pattern can be applied to reduce the load on memory by sharing Objects.

String pool implementation in Java is one of the best examples of flyweight pattern implementation.

Note: Learn more about the Flyweight Pattern.

5. Facade Pattern

The facade pattern is used to help client applications easily interact with the system.

Note: Learn more about the Facade Pattern.

6. Bridge Pattern

When we have interface hierarchies in both interfaces as well as implementations, then the bridge design pattern is used to decouple the interfaces from the implementation and to hide the implementation details from the client programs. The implementation of the bridge design pattern follows the notion of preferring composition over inheritance.

Note: Learn more about the Bridge Pattern.

7. Decorator Pattern

The decorator design pattern is used to modify the functionality of an object at runtime. At the same time, other instances of the same class will not be affected by this, so the individual object gets the modified behavior. The decorator design pattern is one of the structural design patterns (such as adapter pattern, bridge pattern, or composite pattern) and uses abstract classes or interface with the composition to implement. We use inheritance or composition to extend the behavior of an object, but this is done at compile-time, and it’s applicable to all the instances of the class. We can’t add any new functionality to remove any existing behavior at runtime – this is when the decorator pattern is useful.

Note: Learn more about the Decorator Pattern.

Behavioral Design Patterns

Behavioral patterns provide a solution for better interaction between objects and how to provide loose-coupling and flexibility to extend easily.

1. Template Method Pattern

The template method pattern is a behavioral design pattern and is used to create a method stub and to defer some of the steps of implementation to the subclasses. The template method defines the steps to execute an algorithm, and it can provide a default implementation that might be common for all or some of the subclasses.

Note: Learn more about the Template Method Pattern.

2. Mediator Pattern

The mediator design pattern is used to provide a centralized communication medium between different objects in a system. If the objects interact with each other directly, the system components are tightly-coupled with each other which makes maintainability cost higher and not flexible to extend easily. The mediator pattern focuses on providing a mediator between objects for communication and implementing loose-coupling between objects. The mediator works as a router between objects, and it can have its own logic to provide a way of communication.

Note: Learn more about the Mediator Pattern

3. Chain of Responsibility Pattern

The chain of responsibility pattern is used to achieve loose-coupling in software design where a request from the client is passed to a chain of objects to process them. Then the object in the chain will decide who will be processing the request and whether the request is required to be sent to the next object in the chain or not.

We know that we can have multiple catch blocks in a try-catch block code. Here every catch block is kind of a processor to process that particular exception. So when an exception occurs in the try block, it’s sent to the first catch block to process. If the catch block is not able to process it, it forwards the request to the next Object in the chain (i.e., the next catch block). If even the last catch block is not able to process it, the exception is thrown outside of the chain to the calling program.

Note: Learn more about the Chain of Responsibility Pattern.

4. Observer Pattern

An observer design pattern is useful when you are interested in the state of an Object and want to get notified whenever there is any change. In the observer pattern, the Object that watches the state of another Object is called observer, and the Object that is being watched is called subject.

Java provides an built-in platform for implementing the observer pattern through the java.util.Observable class and java.util.Observer interface. However, it’s not widely used because the implementation is limited and most of the time we don’t want to end up extending a class solely for implementing the observer pattern as Java doesn’t provide multiple inheritances in classes. Java Message Service (JMS) uses the observer pattern along with the mediator pattern to allow applications to subscribe and publish data to other applications.

Note: Learn more about the Observer Pattern.

5. Strategy Pattern

Strategy pattern is used when we have multiple algorithms for a specific task, and the client decides the actual implementation be used at runtime. A strategy pattern is also known as a policy pattern. We define multiple algorithms and let client applications pass the algorithm to be used as a parameter.

One of the best examples of this pattern is the Collections.sort() method that takes the Comparator parameter. Based on the different implementations of comparator interfaces, the objects are getting sorted in different ways.

Note: Learn more about the Strategy Pattern.

6. Command Pattern

The command pattern is used to implement loose-coupling in a request-response model. In this pattern, the request is sent to the invoker and the invoker passes it to the encapsulated command object. The command object passes the request to the appropriate method of receiver to perform the specific action.

Note: Learn more about the Command Pattern.

7. State Pattern

The state design pattern is used when an Object changes its behavior based on its internal state. If we have to change the behavior of an Object based on its state, we can have a state variable in the Object and use if-else condition block to perform different actions based on the state. The state pattern is used to provide a systematic and loosely-coupled way to achieve this through context and state implementations.

Note: Learn more about the State Pattern.

8. Visitor Pattern

The visitor pattern is used when we have to perform an operation on a group of similar kinds of objects. With the help of a visitor pattern, we can move the operational logic from the objects to another class.

Note: Learn more about the Visitor Pattern.

9. Interpreter Pattern

The interpreter pattern is used to define a grammatical representation of a language and provides an interpreter to deal with this grammar.

Note: Learn more about the Interpreter Pattern.

10. Iterator Pattern

The iterator pattern is one of the behavioral patterns and is used to provide a standard way to traverse through a group of objects. The iterator pattern is widely used in Java Collection Framework where the iterator interface provides methods for traversing through a Collection. This pattern is also used to provide different kinds of iterators based on our requirements. The iterator pattern hides the actual implementation of traversal through the Collection and client programs use iterator methods.

Note: Learn more about the Iterator Pattern.

11. Memento Pattern

The memento design pattern is used when we want to save the state of an object so that we can restore it later on. This pattern is used to implement this in such a way that the saved state data of the object is not accessible outside of the Object, this protects the integrity of saved state data.

Memento pattern is implemented with two Objects – originator and caretaker. The originator is the Object whose state needs to be saved and restored, and it uses an inner class to save the state of Object. The inner class is called “Memento”, and it’s private so that it can’t be accessed from other objects.

Note: Learn more about the Memento Pattern.

Miscellaneous Design Patterns

There are a lot of design patterns that don’t come under Gang of Four design patterns. Let’s look at some of these popular design patterns.

1. DAO Design Pattern

The Data Access Object (DAO) design pattern is used to decouple the data persistence logic to a separate layer. DAO is a very popular pattern when we design systems to work with databases. The idea is to keep the service layer separate from the data access layer. This way we implement the separation of logic in our application.

Note: Learn more about the DAO Pattern.

2. Dependency Injection Pattern

The dependency injection pattern allows us to remove the hard-coded dependencies and make our application loosely-coupled, extendable, and maintainable. We can implement dependency injection in Java to move the dependency resolution from compile-time to runtime. Spring framework is built on the principle of dependency injection.

Note: Learn more about the Dependency Injection Pattern.

3. MVC Pattern

The Model-View-Controller (MVC) Pattern is one of the oldest and most widely used architectural patterns for creating web applications. It separates the application logic into three interconnected components, each with its own responsibilities. The Model represents the data and business logic, the View is responsible for rendering the user interface, and the Controller handles user input and updates the Model and View accordingly. This separation of concerns makes the MVC pattern ideal for building scalable, maintainable, and flexible web applications.

Pattern Anti-Patterns: When NOT to Use Them

Pattern anti-patterns refer to the misuse or overuse of design patterns, leading to more harm than good. It’s essential to understand when not to apply a design pattern to avoid unnecessary complexity, performance issues, or maintainability problems. Some common scenarios where design patterns might be misused include:

  • Over-engineering: Applying a design pattern to a simple problem, making the solution more complicated than necessary.
  • Premature optimization: Implementing a design pattern to optimize performance before it’s actually needed.
  • Misunderstanding the problem: Applying a design pattern without fully understanding the problem it’s intended to solve.

Here’s an example of over-engineering in Java:

// Over-engineered solution using the Factory pattern for a simple problem
public interface Animal {
    void sound();
}

public class Dog implements Animal {
    @Override
    public void sound() {
        System.out.println("Woof");
    }
}

public class Cat implements Animal {
    @Override
    public void sound() {
        System.out.println("Meow");
    }
}

public class AnimalFactory {
    public static Animal createAnimal(String type) {
        if (type.equals("dog")) {
            return new Dog();
        } else if (type.equals("cat")) {
            return new Cat();
        } else {
            return null;
        }
    }
}

// Usage
Animal animal = AnimalFactory.createAnimal("dog");
animal.sound(); // Output: Woof

// A simpler solution without the Factory pattern
public class Animal {
    public static void sound(String type) {
        if (type.equals("dog")) {
            System.out.println("Woof");
        } else if (type.equals("cat")) {
            System.out.println("Meow");
        }
    }
}

// Usage
Animal.sound("dog"); // Output: Woof

Integration with Modern Java (e.g., Java 8+ Features like Lambdas with Strategy)

Modern Java features, such as lambda expressions, method references, and functional programming, can be leveraged to simplify the implementation of design patterns. For example, the Strategy pattern can be implemented more concisely using lambda expressions, making the code more expressive and easier to read.

Here’s an example of using lambda expressions to implement the Strategy pattern in Java:

// Traditional Strategy pattern implementation
public interface SortingStrategy {
    void sort(int[] array);
}

public class BubbleSortStrategy implements SortingStrategy {
    @Override
    public void sort(int[] array) {
        // Bubble sort implementation
    }
}

public class QuickSortStrategy implements SortingStrategy {
    @Override
    public void sort(int[] array) {
        // Quick sort implementation
    }
}

public class Sorter {
    private SortingStrategy strategy;

    public Sorter(SortingStrategy strategy) {
        this.strategy = strategy;
    }

    public void setStrategy(SortingStrategy strategy) {
        this.strategy = strategy;
    }

    public void sort(int[] array) {
        strategy.sort(array);
    }
}

// Usage
Sorter sorter = new Sorter(new BubbleSortStrategy());
sorter.sort(new int[]{5, 2, 8, 3, 1, 6, 4});

// Using lambda expressions to implement the Strategy pattern
public class SorterLambda {
    public void sort(int[] array, SortingStrategy strategy) {
        strategy.sort(array);
    }
}

// Usage
SorterLambda sorterLambda = new SorterLambda();
sorterLambda.sort(new int[]{5, 2, 8, 3, 1, 6, 4}, (int[] arr) -> {
    // Bubble sort implementation
});

Relationship with SOLID Principles

SOLID principles are a set of design principles aimed at promoting cleaner, more maintainable code. They are closely related to design patterns, as many patterns are designed to adhere to these principles. The SOLID principles are:

  • Single Responsibility Principle (SRP): A class should have only one reason to change.
  • Open-Closed Principle (OCP): A class should be open for extension but closed for modification.
  • Liskov Substitution Principle (LSP): Subtypes should be substitutable for their base types.
  • Interface Segregation Principle (ISP): A client should not be forced to depend on interfaces it does not use.
  • Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules, but both should depend on abstractions.

By following SOLID principles, developers can create more maintainable, flexible, and scalable software systems that are easier to extend and modify over time.

Common Errors and Debugging

Global state abuse

Global state abuse occurs when a program relies too heavily on global variables, making it difficult to understand and maintain. To fix this issue:

  • Minimize the use of global variables by passing necessary data as parameters to functions or methods.
  • Consider using a singleton pattern or a dependency injection framework to manage global state in a more controlled manner.

Example of minimizing global variables:

// Before
public class MyClass {
    private static int globalVariable = 0;
    public void doSomething() {
        globalVariable++;
    }
}

// After
public class MyClass {
    private int localVariable = 0;
    public void doSomething() {
        localVariable++;
    }
}

Using patterns where unnecessary

Using design patterns where they are not needed can lead to over-engineering and increased complexity. To avoid this:

  • Only apply design patterns when they solve a specific problem or improve code readability and maintainability.
  • Consider the KISS principle (Keep It Simple, Stupid) and opt for the simplest solution that meets the requirements.

Example of applying the KISS principle:

// Before (over-engineered)
public class ComplexCalculator {
    private CalculatorStrategy strategy;
    public ComplexCalculator(CalculatorStrategy strategy) {
        this.strategy = strategy;
    }
    public int calculate(int a, int b) {
        return strategy.calculate(a, b);
    }
}

// After (simplified)
public class SimpleCalculator {
    public int calculate(int a, int b) {
        return a + b;
    }
}

Misapplied structural patterns

Misapplying structural patterns can lead to tight coupling and rigid code structures. To correct this:

  • Ensure that the chosen structural pattern aligns with the problem domain and the requirements of the system.
  • Consider alternative patterns or a combination of patterns to achieve a more flexible and maintainable design.

Example of aligning patterns with the problem domain:

// Before (misapplied pattern)
public class User {
    private List<Order> orders;
    public List<Order> getOrders() {
        return new ArrayList<>(orders);
    }
}

// After (aligned pattern)
public class User {
    private OrderService orderService;
    public List<Order> getOrders() {
        return orderService.getOrdersForUser(this);
    }
}

Not handling concurrency

Failing to handle concurrency can result in race conditions, deadlocks, and other synchronization issues. To address this:

  • Identify areas of the code that require concurrent access and implement appropriate synchronization mechanisms such as locks, semaphores, or atomic operations.
  • Consider using higher-level concurrency abstractions like parallel streams or concurrent collections to simplify concurrent programming.

Example of handling concurrency with locks:

public class ConcurrentCounter {
    private int count = 0;
    private final Object lock = new Object();
    public void increment() {
        synchronized (lock) {
            count++;
        }
    }
    public int getCount() {
        synchronized (lock) {
            return count;
        }
    }
}

Not unregistering observers

Failing to unregister observers can lead to memory leaks and unexpected behavior. To fix this:

  • Ensure that observers are properly unregistered when they are no longer needed or when the observed object is being destroyed.
  • Consider using a weak reference to observers to prevent strong references from keeping them alive unnecessarily.

Example of unregistering observers:

public class Observable {
    private List<Observer> observers = new ArrayList<>();
    public void addObserver(Observer observer) {
        observers.add(observer);
    }
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

FAQs

1. What are design patterns in Java?

Design patterns in Java are reusable solutions to common problems that arise during software design. They provide a proven, standardized approach to solving a specific design problem, making it easier to write maintainable, flexible, and scalable code.

Example: The Singleton pattern ensures that only one instance of a class is created, which can be useful for managing resources or providing a global point of access.

public class Singleton {
    private static Singleton instance;
    private Singleton() {}
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

2. How many design patterns are there in Java?

There are 23 classic design patterns as described in the Gang of Four book, which are categorized into three types: Creational, Structural, and Behavioral patterns. However, there are many more patterns that have been identified and documented over time.

3. What is the most used design pattern in Java?

The most used design pattern in Java is likely the Singleton pattern, as it is simple to implement and provides a straightforward way to manage global access to a resource.

Example: The Singleton pattern is often used for logging mechanisms, where a single instance of a logger is needed throughout the application.

public class Logger {
    private static Logger instance;
    private Logger() {}
    public static synchronized Logger getInstance() {
        if (instance == null) {
            instance = new Logger();
        }
        return instance;
    }
    public void log(String message) {
        System.out.println(message);
    }
}

4. Is it OK to mix design patterns in a single project?

Yes, it is common and acceptable to mix design patterns in a single project. In fact, combining patterns can lead to more robust and maintainable software systems. For example, using the Factory pattern to create objects and the Singleton pattern to manage access to a resource.

Example: Mixing the Factory and Singleton patterns to create a logging system that uses a single instance of a logger, but allows for different types of loggers to be created dynamically.

public class LoggerFactory {
    private static LoggerFactory instance;
    private LoggerFactory() {}
    public static synchronized LoggerFactory getInstance() {
        if (instance == null) {
            instance = new LoggerFactory();
        }
        return instance;
    }
    public Logger createLogger(String type) {
        if (type.equals("console")) {
            return new ConsoleLogger();
        } else if (type.equals("file")) {
            return new FileLogger();
        } else {
            return null;
        }
    }
}

5. Are design patterns still relevant in modern Java (Java 8+)?

Yes, design patterns are still relevant in modern Java (Java 8+). While Java 8 introduced functional programming features and lambda expressions, which can simplify some design patterns, the core principles of design patterns remain unchanged. Design patterns continue to provide a way to write maintainable, scalable, and efficient code, making them essential for any Java developer.

Conclusion

This article provided an overview of Java design patterns, covering their importance, examples, and best practices. It also explored the relevance of design patterns in modern Java development and their relationship with SOLID principles.

To dive deeper into specific design patterns, refer to the following tutorials:

You can also explore Java design patterns example code from our GitHub Repository.

For further learning, browse through our collection of Java tutorials to improve your skills and stay up-to-date with the latest developments in Java technology.

Continue building with DigitalOcean Gen AI Platform.

About the author(s)

Pankaj Kumar
Pankaj Kumar
See author profile
Category:
Tutorial

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
JournalDev
DigitalOcean Employee
DigitalOcean Employee badge
August 17, 2013

Hey your blog is really good. congratulation

- Nestor

    JournalDev
    DigitalOcean Employee
    DigitalOcean Employee badge
    August 27, 2013

    Hi Pankaj, Thanks a lot for the tutorial. Could you please also include J2EE Design Patterns also. (MVC, Business Delegates…)

    - Syam

      JournalDev
      DigitalOcean Employee
      DigitalOcean Employee badge
      August 28, 2013

      Hi your blog is really good, I request you to post more on struts2 from basics to advance.

      - Naveen

        JournalDev
        DigitalOcean Employee
        DigitalOcean Employee badge
        January 22, 2014

        This tutorial was very helpful ; thanks Pankaj …waiting for J2EE Design Patterns tutorial

        - Varun

          JournalDev
          DigitalOcean Employee
          DigitalOcean Employee badge
          March 4, 2014

          One of the best article on Design pattern, Thanks.

          - Usha

            JournalDev
            DigitalOcean Employee
            DigitalOcean Employee badge
            March 5, 2014

            Excellent Explanation, Wonderful examples to understood easily… Thanks

            - Ravikumar

              JournalDev
              DigitalOcean Employee
              DigitalOcean Employee badge
              March 12, 2014

              This is a really good blog, and I always follow your blog whenever I need any clearance.

              - job@basware.com

                JournalDev
                DigitalOcean Employee
                DigitalOcean Employee badge
                April 9, 2014

                Excellent site to know about all design patterns.

                - SRK

                  JournalDev
                  DigitalOcean Employee
                  DigitalOcean Employee badge
                  April 20, 2014

                  Great work! Thanks.

                  - chandrani

                    JournalDev
                    DigitalOcean Employee
                    DigitalOcean Employee badge
                    April 26, 2014

                    This is excellent work sir !!! really helpful

                    - Delli Babs

                      Join the Tech Talk
                      Success! Thank you! Please check your email for further details.

                      Please complete your information!

                      Become a contributor for community

                      Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

                      DigitalOcean Documentation

                      Full documentation for every DigitalOcean product.

                      Resources for startups and SMBs

                      The Wave has everything you need to know about building a business, from raising funding to marketing your product.

                      Get our newsletter

                      Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

                      New accounts only. By submitting your email you agree to our Privacy Policy

                      The developer cloud

                      Scale up as you grow — whether you're running one virtual machine or ten thousand.

                      Get started for free

                      Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

                      *This promotional offer applies to new accounts only.