Can AI Understand Design Patterns? Exploring GitHub Copilot’s Role in Clean Code and Architecture for Java Developers

Artificial Intelligence is reshaping how we write code and with GitHub Copilot, which is powered by OpenAI’s Codex model, promises to be more than just an autocomplete tool. 

It claims to “understand” context, and even the intent. But can it really grasp design patterns, SOLID principles, and Clean Architecture? Concepts that are more about architectural thinking than line-by-line logic?

As Java developers, we care deeply about maintainability, scalability, and writing clean code. Let’s explore whether Copilot “gets it” and how far we can go by letting it drive design-centric coding decisions.

If you’re new to Copilot, I’d recommend this free course : GitHub Copilot for Java Developers.

1. Real Code Experiments: Can Copilot Write Design Patterns?

To understand Copilot’s strengths and limitations, I ran a series of experiments asking it to generate some of the common Java design patterns from minimal prompts. Here’s what I observed:

1.1 Factory Pattern

Prompt I entered:

// Create a simple factory pattern in Java

Copilot Output:

interface Shape {

    void draw();

}


class Circle implements Shape {

    public void draw() {

        System.out.println("Drawing Circle");

    }

}


class Rectangle implements Shape {

    public void draw() {

        System.out.println("Drawing Rectangle");

    }

}


class ShapeFactory {

    public Shape getShape(String shapeType) {

        if (shapeType.equals("CIRCLE")) {

            return new Circle();

        } else if (shapeType.equals("RECTANGLE")) {

            return new Rectangle();

        }

        return null;

    }

}

Verdict: Accurate. Copilot nails the Factory Pattern basics. It understands interfaces and encapsulation well.

1.2 Builder Pattern

Prompt:

// Implement the Builder pattern for a complex object like a Computer

Copilot Output (partial):

class Computer {

    private String CPU;

    private String RAM;

    private String storage;


    public static class Builder {

        private String CPU;

        private String RAM;

        private String storage;


        public Builder setCPU(String CPU) {

            this.CPU = CPU;

            return this;

        }


        public Builder setRAM(String RAM) {

            this.RAM = RAM;

            return this;

        }


        public Builder setStorage(String storage) {

            this.storage = storage;

            return this;

        }


        public Computer build() {

            Computer c = new Computer();

            c.CPU = this.CPU;

            c.RAM = this.RAM;

            c.storage = this.storage;

            return c;

        }

    }

}

Verdict: Spot on. Copilot followed the standard builder pattern idiom, chaining setters and encapsulating object creation.

1.3 Strategy Pattern

Prompt:

// Write a Strategy pattern for different payment methods

Copilot Output:

interface PaymentStrategy {

    void pay(int amount);

}


class CreditCardPayment implements PaymentStrategy {

    public void pay(int amount) {

        System.out.println("Paid with Credit Card: " + amount);

    }

}


class PayPalPayment implements PaymentStrategy {

    public void pay(int amount) {

        System.out.println("Paid with PayPal: " + amount);

    }

}


class PaymentContext {

    private PaymentStrategy strategy;

    public void setStrategy(PaymentStrategy strategy) {

        this.strategy = strategy;

    }

    public void pay(int amount) {

        strategy.pay(amount);

    }

}

Verdict: Clear and usable. Copilot understands behavioral patterns and separates concerns well.

2. Where the Copilot Struggles!

GitHub Copilot is fast, confident, and frequently right. But not always! Especially when it comes to architectural decisions, subtle design trade-offs, or domain-specific requirements.

Let’s break this down into situations where Copilot is helpful and where it might mislead you.

2.1. When to Trust Copilot

I noticed, Copilot performs reliably well when dealing with:

  • Standard design patterns like Factory, Strategy, Singleton, Builder
  • Layered architectures (Controller, Service, Repository)
  • JPA annotations and Spring Boot conventions
  • DTOs, mappers, and POJOs

It has been trained on tons of open-source codebases on GitHub, so it recognizes patterns that are widely used across Java projects.

2.2. Repetitive Boilerplate

Whether it’s generating equals()/hashCode(), REST endpoint mappings, or Lombok-less getters/setters, Copilot is excellent at removing grunt work. We can reliably let it take care of mundane tasks so we can focus on actual design thinking.

2.3. Smart Autocompletion in Context

When working within a consistent module or service layer, Copilot can often reliably predict the next few lines of code with high accuracy. It “remembers” class and method names you’ve recently used and auto-suggests them with surprising accuracy.

3 When to Take the Lead

3.1. Domain-Driven Design (DDD) and Rich Domain Models

Copilot doesn’t understand your domain. It simply can’t. It may create an anemic model or put logic in the wrong layer.

As an example, it might suggest placing business validation inside a controller instead of a domain service or aggregate root which we normally do.

You must enforce your boundaries and model richness otherwise Copilot will not know the difference.

3.2. Architectural Trade-offs

Copilot won’t ask questions like:

  • Should this component be stateless or not?
  • Should I use an interface here and enable future extensibility?
  • Will this tight coupling cause any issues in testing?

It basically defaults to the most common patterns in public codebases, which may not align well with the long-term needs of your application.

3.3. Coding with Intent and Constraints

Copilot may:

  • Use inheritance where composition is clearly better
  • Suggest synchronous operations when asynchronous design is apt
  • Ignore scalability, memory footprint, or performance implications of our app

This is where your intent as an engineer must override its output.

3.4. Team or Project-Specific Conventions

Copilot doesn’t understand your team’s architectural playbook, or know your code review standards, or project-specific constraints. It simply won’t:

  • Respect your naming conventions
  • Follow domain-specific abstractions
  • Avoid deprecated APIs unless explicitly instructed by you

Without strong linting, unit tests, or CI enforcement, these small deviations can snowball over time.

 

4. Can Copilot Help You Prototype Architecture?

Definitely! Especially during the ideation phase.

Say you’re planning a layered Clean Architecture structure with:

  • ControllerUseCaseDomain ModelRepository

You can scaffold interfaces and class stubs quickly with Copilot:

Prompt:

// Create Clean Architecture layers for a Task Management System

 

Copilot might output:

// Domain

class Task {

    private String id;

    private String description;

    private boolean completed;

}


// UseCase

interface CreateTaskUseCase {

    void execute(Task task);

}


// Repository

interface TaskRepository {

    void save(Task task);

}


// Controller

class TaskController {

    private CreateTaskUseCase createTaskUseCase;


    public void createTask(Task task) {

        createTaskUseCase.execute(task);

    }

}

Instead of writing boilerplate, you spend time on decisions, not syntax.

5. Challenges Copilot Faces with Abstract Thinking in Large Systems

While GitHub Copilot does an excellent job at pattern recognition and syntax generation, it faces notable challenges when stepping into the realm of architectural thinking. Especially in large, enterprise-grade systems.

5.1 Lack of Context Awareness

Copilot operates at the token and prompt level. It doesn’t really “understand” the broader context of your application. For example, if you have a complex domain model with very subtle business rules, Copilot may:

  • Suggest class or method names that might conflict with your domain language
  • Duplicate logic already handled elsewhere in the code
  • Misplace responsibilities across layers (e.g., putting business logic in controllers for instance)

In large systems where consistency and cohesion are important, Copilot might unintentionally introduce architecture drift.

5.2 Shallow Understanding of SOLID Principles

Even when Copilot writes code that seems to follow SOLID, it often does so superficially.

  • SRP (Single Responsibility Principle): Copilot might lump unrelated methods into one class file unless prompted very clearly.
  • OCP (Open/Closed Principle): It doesn’t proactively create extensible abstractions on its own, unless you explicitly ask for an interface-driven design.
  • LSP, ISP, DIP: These are nuanced principles that require systemic understanding of how modules interact, which Copilot lacks unless heavily guided by devs..

5.3 Refactoring Limitations

Copilot isn’t good at refactoring for intent. It can rename, restructure, or reformat. But it won’t naturally suggest splitting a bloated class into focused responsibilities, unless your prompt is crystal clear.

Example:

Prompting Copilot with
// Refactor this class using SRP
might give you mixed results. Sometimes it might split things logically, other times it just renames the class and calls it a day.

Summary of Challenges:

Challenge Area Copilot Limitation
Large system understanding No awareness of app-wide structure
Domain logic Treats everything generically
Architectural decisions Needs step-by-step prompting
Deep SOLID compliance Lacks strategic application
Meaningful refactoring Requires human oversight

6. Final Thoughts

Sure! Here’s a more personal, first-person version of the summary:

From my experience, Copilot may not be a software architect, but it’s definitely a handy assistant. I’ve found it very useful for generating design pattern templates, cleaning up common code smells (with a bit of guidance), and quickly prototyping architectural ideas. It’s like someone taking care of the repetitive bits so I can focus on the bigger picture.

To me, Copilot feels more like a junior developer who’s memorized all the standard patterns but doesn’t really understand the context or the business logic behind what we’re really building. It’s great for getting started, but I always make sure to stay in the driver’s seat.

At the end of the day, clean architecture and sound design decisions still mainly rely on our experience and judgment.

 

Leave a Reply