Last Updated: February 13, 2026
Have you ever changed one part of your code... and suddenly, five unrelated things broke?
Or added a small feature... and ended up editing dozens of lines across a single class?
If yes, you’ve probably encountered a violation of one of the most important design principles in software engineering: The Single Responsibility Principle (SRP).
Let's understand it by looking at a common problem and seeing exactly why it violates SRP.
You have probably seen this kind of class before. Maybe you have even written one. It is called a God Class because it tries to do everything, and like most things that try to do everything, it does nothing particularly well.
At first glance, this may seem convenient. Everything about user registration is in one place. But let's pause and examine what this class is actually doing:
That’s four distinct responsibilities rolled into one class. And each one introduces a separate reason for the class to change:
This class is tightly coupled to four different reasons to change. That’s a red flag.
A class should have one, and only one, reason to change. — Robert C. Martin (Uncle Bob)
In simple words: A class should do one thing and do it well.
The Single Responsibility Principle (SRP) is the "S" in the famous SOLID principles of object-oriented design. It is the first principle for a good reason: it forms the foundation for writing maintainable, testable, and flexible code.
But what exactly is a "responsibility"?
It’s not a method. It’s not a function. It’s a reason for the class to change.
Ask yourself this:
How many reasons might someone need to update this class in the future?
If the answer is more than one, the class is likely breaking SRP.
Think of a restaurant. Would you hire one person to do all of these?
Of course not. You would hire a chef, a waiter, a cleaner, and an accountant. Each with a single responsibility.
Why should your code be any different?
You immediately understand what the class is supposed to do. No surprises, no scrolling through hundreds of lines to find the method you need.
Smaller responsibilities mean smaller test cases and fewer dependencies. You can test a password hasher without needing a database connection or an email server.
Changes in one responsibility don't ripple across unrelated parts of the code. Updating the email service does not risk breaking password hashing.
Small, focused classes are more flexible and can be reused in different contexts. An AuthTokenService can serve both a web application and a mobile API.
Teams can own different parts of the system without stepping on each other's toes. The database team can modify UserRepository without worrying about breaking the email workflow.
These benefits compound over time. A codebase that follows SRP from the start is far easier to extend six months later than one full of God Classes.
Time to fix our original UserService God Class using SRP. We will take each distinct responsibility from the original class and extract it into its own focused, well-named class.
They all deserve their own classes. Let's walk through each one.
Let's start by extracting a simple User data class. Its only job is to represent a user.
This class now does one thing: represent a user. It does not hash passwords, store itself, or send emails. That is the job of others.
This class handles just the logic of validating and hashing a password.
If the hashing algorithm changes (say, from bcrypt to argon2), we only update this class. Nothing else is affected.
The responsibility for talking to the database belongs here.
You can swap out JDBC for JPA, switch from SQL to a NoSQL store, or change your ORM entirely without touching the rest of the system.
This class handles the creation of authentication tokens.
You can switch from JWT to opaque tokens, change the signing algorithm, or adjust token expiry without affecting the rest of your codebase. The auth logic is completely isolated from password hashing, database operations, and email delivery.
This class is responsible only for sending emails.
It does not hash passwords or generate tokens. It just sends the welcome email. If you need to switch from SMTP to a third-party email API, this is the only class you touch.
SRP sounds straightforward in theory, but developers run into several traps when applying it in practice. Let's look at the most common ones and how to avoid them.
The mistake here is breaking a class into too many tiny classes that don't add real value.
For example, creating separate classes for PasswordValidator, SaltGenerator, BcryptEncoder, and HashAggregator when all of these could be grouped into a cohesive PasswordHasher.
Why is this a problem? It leads to unnecessary complexity, makes the system harder to understand, and increases overhead in navigating and wiring too many classes together.
Each new class adds a file, an import, and a conceptual dependency that someone has to hold in their head.
Focus on cohesion, not fragmentation. Group logic that changes together or belongs to the same business concern.
Sometimes developers assume each method must be its own class. Consider an EmailService that can send different types of emails.
Some developers might try to split this into WelcomeEmailSender and PayslipEmailSender. But both methods deal with the same responsibility: sending emails. Splitting them adds more boilerplate without clear benefits.
Don't confuse the number of methods with the number of responsibilities. If the methods serve the same purpose (sending emails), it is fine to keep them together.
The mistake is thinking, "This class is small and works fine, no need to split it." But a utility class that starts off simple can quietly grow into a mess.
These responsibilities often evolve independently. The CSV format might change because of a new business requirement, the email service might switch providers, and the archive strategy might move from local storage to cloud. Small changes to one feature might introduce bugs in others.
Watch for creeping responsibilities even in utility classes. Apply SRP early, before small classes become unmanageable.
The mistake is taking the "reason to change" definition too literally or too vaguely.
A bad interpretation would be: "I only ever change this class when a stakeholder asks for a change, so it has one reason to change."
SRP is not about who asks for the change, but what kind of change is being made. A class that handles both password hashing and database persistence has two reasons to change, even if the same product manager requests both updates.
Clarify the responsibility in terms of business logic or technical behavior. Ask: Is this logic cohesive, or are unrelated concerns bundled together?
Yes, you’ll likely end up with more classes but that’s not a bad thing.
Instead of having one massive class doing everything poorly, you have smaller, focused classes doing one thing well. These classes are:
Think of it as managing complexity through separation, not increasing it. When responsibilities are clearly separated, your system becomes easier to reason about even if the file count grows.
SRP helps reduce cognitive load, even if it increases the class count.
There’s no hard-and-fast rule. It depends on your domain and use case. But here’s a simple heuristic:
If you need to use the word “and” or “or” to describe what your class does, it probably has more than one responsibility.
Example:
Another tip: If the reasons for change are unrelated say, a tax policy update vs. a new email template, your class is likely doing too much.
Absolutely. SRP can and should be applied across multiple levels:
SRP is a mindset: separate concerns to improve clarity and adaptability, no matter the scale.
When a class does only one thing, testing becomes straightforward.
You don’t have to:
You can focus on the specific input/output of a class without worrying about unrelated functionality baked into it.
Sometimes it’s okay to group closely related behaviors into one class.
For example, a EmailService class that:
That’s fine. They all fall under the same responsibility: sending emails. But if that class also starts doing PDF generation or user authentication, it’s time to split it up.
Think of SRP less as a strict rule and more as a guiding principle. It won’t always be obvious where to draw the line, and that’s okay.
Use SRP to:
When used wisely, SRP becomes a tool to manage change and complexity, not a burden.