Java is one of the most widely-used object-oriented programming languages today, known for its robustness, cross-platform compatibility, and user-friendly syntax. Java developers are responsible for creating a wide range of software products that fuel our digital world, from web applications to mobile apps, desktop software, and enterprise solutions and again a question arises how many oops concepts in Java are there? However, any good developer must write code that is not only functional but also modular, easy to test, debug, and maintain. This is where the principles of object-oriented design come into play. By following these regulations, developers may ensure that their code is clean, efficient, and simple to use, both for themselves and for those who may need to work with it in the future.

Now continuing with the article, we’ll discuss some of the best object-oriented design principles that Java programmers should learn in 2023 to take their skills to the next level and write code that is maintainable, scalable, and efficient.
7 OOP Design Principles For Java Programmers
1. DRY – Don’t Repeat Yourself
DRY is an acronym for Don’t Repeat Yourself. As the name suggests this principle focuses on reducing the duplication of the same code throughout the program. If you have the same block of code, performing the same tasks in multiple parts of the program, then it means that you are not following the DRY principle. The DRY principle can be implemented by refactoring the code such that it removes duplication and redundancy by creating a single reuseable code in the form of abstraction, or a function.
Example: 1.1. Before DRY
Java
import java.io.*;
public class GFG {
public static void main(String[] args)
{
int [][] matrix = { { 1 , 2 , 3 , 4 },
{ 5 , 6 , 7 , 8 },
{ 9 , 10 , 11 , 12 },
{ 13 , 14 , 15 , 16 } };
System.out.println( "Matrix1: " );
for ( int i = 0 ; i < 4 ; i++) {
for ( int j = 0 ; j < 4 ; j++) {
System.out.print(matrix[i][j] + " " );
}
System.out.println();
}
int [][] matrix2 = { { 1 , 2 , 3 , 4 , 5 },
{ 6 , 7 , 8 , 9 , 10 },
{ 11 , 12 , 13 , 14 , 15 },
{ 16 , 17 , 18 , 19 , 20 },
{ 21 , 22 , 23 , 24 , 25 } };
System.out.println( "Matrix2: " );
for ( int i = 0 ; i < 5 ; i++) {
for ( int j = 0 ; j < 5 ; j++) {
System.out.print(matrix2[i][j] + " " );
}
System.out.println();
}
}
}
|
Output
Matrix1:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
Matrix2:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
In Example 1.1, the code used for printing the matrix of different lengths. But the code block is repeated twice, once for printing the matrix of length 4×4 and the other for the matrix of length 5×5, which does not follow the DRY guidelines.
1.2. After DRY
Java
import java.io.*;
public class GFG {
private void printMatrix( int [][] matrix)
{
int n = matrix.length;
int m = matrix[ 0 ].length;
for ( int i = 0 ; i < n; i++) {
for ( int j = 0 ; j < m; j++) {
System.out.print(matrix[i][j] + " " );
}
System.out.println();
}
}
public static void main(String[] args)
{
GFG obj = new GFG();
int [][] matrix = { { 1 , 2 , 3 , 4 },
{ 5 , 6 , 7 , 8 },
{ 9 , 10 , 11 , 12 },
{ 13 , 14 , 15 , 16 } };
System.out.println( "Matrix1: " );
obj.printMatrix(matrix);
int [][] matrix2 = { { 1 , 2 , 3 , 4 , 5 },
{ 6 , 7 , 8 , 9 , 10 },
{ 11 , 12 , 13 , 14 , 15 },
{ 16 , 17 , 18 , 19 , 20 },
{ 21 , 22 , 23 , 24 , 25 } };
System.out.println( "Matrix2: " );
obj.printMatrix(matrix2);
}
}
|
Output
Matrix1:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
Matrix2:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
The repetition from Example 1.1 is resolved in Example 1.2 where a method `printMartix()` is created to print the matrix of any dimensions and then the repetitive block is placed inside of it. Now, this method can then be called as many times as required to print the matrix of any possible dimensions, with just one line of code.
Must Read – DRY (Don’t Repeat Yourself) Principle in Java with Examples
2. OCP – Open-Closed Principle
OCP is an acronym for Open Closed Principle, it is also considered as a basic principle of OOPs. According to this principle, all the entities, like classes, methods, etc, should be open for extensions but closed for modifications. This means that you have to keep your code open for extending the behavior, but it should not allow modification of the existing source code. The principle emphasizes the statement by Robert C. Martin, “If already tried and tested code is not touched, it won’t break”.
Example: 2.1. Before OCP
Java
import java.io.*;
class Calculator {
public double calculate( double a, double b,
char operator)
{
switch (operator) {
case '+' :
return a + b;
case '-' :
return a - b;
}
return 0.0 ;
}
}
public class GFG {
public static void main(String args[])
{
Calculator obj = new Calculator();
System.out.println(obj.calculate( 10 , 20 , '+' ));
System.out.println(obj.calculate( 10 , 20 , '-' ));
}
}
|
In example 2.1, the `Calculator` class is used used to perform some arithmetic operations on the provided numbers. The operation is specified by the char `operator`. Currently, the Class only supports 2 operations i.e. addition and subtraction. If a new operation is to be added, it would need to modify the existing implementation of the ‘Calculator’ class, which does not follow the `Open Closed Principle (OCP)`.
2.2. After OCP
Java
import java.io.*;
interface Arithmetic {
double perform( double a, double b);
}
class Addition implements Arithmetic {
public double perform( double a, double b)
{
return a + b;
}
}
class Substraction implements Arithmetic {
public double perform( double a, double b)
{
return a - b;
}
}
class Calculator {
public double calculate(Arithmetic arithmetic, double a,
double b)
{
return arithmetic.perform(a, b);
}
}
public class GFG {
public static void main(String[] args)
{
Calculator obj = new Calculator();
System.out.println(
obj.calculate( new Addition(), 10 , 20 ));
System.out.println(
obj.calculate( new Substraction(), 10 , 20 ));
}
}
|
The need for modification of a class in Example 2.1 is removed in Example 2.2. The interface `Arithmetic` is declared which declares a `perform()` method. Two classes Addition and Subtraction are also defined which implement the Arithmetic interface and provide their own implementation of `perform()` the method.
In the same way, other classes can be created for Multiplication or any other operation, by implementing the `Arithmetic` interface and providing their own version of the `perform()` method. The approach follows Open Closed Principle (OCP) as we can extend the functionality of the code by modifying the original code.
Must Read – Open Closed Principle in Java with Examples
3. SRP – Single Responsibility Principle
SRP is an acronym for Single Responsibility Principle. This principle suggests that a `Class` should have only one reason to change. This means that a `Class` should only implement one functionality and only change when there is a need to change the functionality. If a class has too many responsibilities, it becomes difficult to manage in the long run. With SRP the classes become more modular and focused, leading to a more maintainable and flexible code.
Example – 3.1. Before SRP
Java
class User {
String name;
String email;
public User(String name, String email)
{
this .name = name;
this .email = email;
}
public void showUser()
{
System.out.println( "Name: " + this .name);
System.out.println( "Email: " + this .email);
}
public void sendEmail(String message)
{
System.out.println( "Email sent to " + this .email
+ " with message: " + message);
}
public void saveToFile()
{
System.out.println( "Saving user to file..." );
}
}
public class GFG {
public static void main(String[] args)
{
user1.showUser();
user1.sendEmail( "Hello John" );
user1.saveToFile();
}
}
|
In Example 3.1, the User class is used to perform multiple responsibilities, including displaying user information with the `showUser()` method, sending email to the user with `sendEmail()`, and saving user data to a file with the `saveFile()` method. This clearly violates the Single Responsibility Principle (SRP), as a class should have only one reason to change. If there are multiple responsibilities a change in one of the functionalities (like a change in file storage method) can potentially break existing behaviors, necessitating another round of testing to avoid unexpected behavior in production.
3.2. After SRP
Java
class User {
String name;
String email;
public User(String name, String email)
{
this .name = name;
this .email = email;
}
public void showUser()
{
System.out.println( "Name: " + this .name);
System.out.println( "Email: " + this .email);
}
}
class EmailService {
public void sendEmail(User user, String message)
{
System.out.println( "Email sent to " + user.email
+ " with message: " + message);
}
}
class FileService {
public void saveToFile(User user)
{
System.out.println( "Saving user to file..." );
}
}
public class GFG {
public static void main(String[] args)
{
user.showUser();
EmailService emailService = new EmailService();
emailService.sendEmail(user, "Hello John" );
FileService fileService = new FileService();
fileService.saveToFile(user);
}
}
|
In Example 3.2, all the services are moved into their individual classes. The `User` class is used to display the user data, the `FileService` class is used save the user to the file, and `EmailService` is used to send Emails to the user. This was one class is only responsible for managing a single functionality. By doing this, we have ensured that each class has only one reason to change and is adhering to the Single Responsibility Principle (SRP) guidelines.
Must Read – Single Responsibility Principle in Java with Examples
4. ISP – Interface Segregation Principle
ISP is an acronym for Interface Segregation Principle. The principle states that clients should not forcefully implement an Interface if it does not use that. This means that the class should not implement an interface if the methods declared by the interface are not used by the class. Similar to SRP, this principle states that one should focus on creating multiple client interfaces responsible for a particular task, rather than having a one-fat interface.
Example – 4.1. Before ISP
Java
interface Animal{
public void breath();
public void fly();
public void swim();
}
class Fish implements Animal{
public void swim(){
System.out.println( "Fish swims" );
}
public void fly(){
}
}
|
In Example 4.1, the `Animal` interface has two methods: `fly()`, and `swim()`. The `Fish` class implements all two methods, but the fly() method does not make sense for a fish since they do not fly. Hence, this code breaks the Interface Segregation Principle (ISP) because the Animal interface is too generic and has methods that are not relevant to all animals. This can lead to unnecessary complexity and confusion in the implementation of classes that implement the interface
4.2. After ISP
Java
interface Animal {
public void breath();
}
interface WaterAnimal {
public void swim();
}
interface AirAnimal {
public void fly();
}
class Fish implements WaterAnimal {
public void swim() {
System.out.println( "Fish swims" );
}
}
|
In Example 4.2, the Animal interface is broken down into more specific interfaces, WaterAnimal and AirAnimal. The Fish class now implements only the WaterAnimal interfaces, similarly AirAnimal interface can be used for the animals that fly, for example, Birds. This way, each interface only contains methods that are relevant to the types of animals that implement them. In this way the code follows the Interface Segregation Principle, making the code more modular and hence manageable.
5. LSP – Liskov Substitution Principle
LSP is an acronym for Liskov Substitution Principle. According to the principle, Derived classes must be substitutable for their base classes. This indicates that superclass objects in the program should be interchangeable by instances of their subclasses without compromising the program’s correctness. It guarantees that any child class of a parent class can be used in place of their parent without causing any unexpected behavior.
Example – 5.1. Before LSP
Java
class Rectangle {
int length;
int width;
void setLength( int l) {
length = l;
}
void setWidth( int w) {
width = w;
}
int area() {
return length * width;
}
}
class Square extends Rectangle {
void setLength( int l) {
length = l;
width = l;
}
void setWidth( int w) {
length = w;
width = w;
}
}
public class GFG {
public static void main(String[] args) {
Rectangle obj = new Square();
obj.setLength( 5 );
obj.setWidth( 10 );
System.out.println(obj.area());
}
}
|
In Example 5.1, `Square` is a base class that inherits from the parent `Rectangle` class. In the `Rectangle` class, the `setLength()` and `setWidth()` methods set the length and width of the rectangle respectively. However, in the Square class, these methods set both the length and width to the same value, which tells that a Square object cannot be substituted for a Rectangle object in all cases. Hence the above code violates LSP, this can be verified with the code in GFG class that uses the `Square` object as a Rectangle object which leads to unexpected behavior.
5.2. After LSP
Java
interface Shape{
void setLength( int l);
void setWidth( int w);
int area();
}
class Rectangle implements Shape{
int length;
int width;
public void setLength( int l) {
length = l;
}
public void setWidth( int w) {
width = w;
}
public int area() {
return length * width;
}
}
public class GFG {
public static void main(String[] args) {
Shape obj = new Rectangle();
obj.setLength( 5 );
obj.setWidth( 10 );
System.out.println(obj.area());
}
}
|
In Example 5.2, the Square class is removed, and a Shape interface is introduced with methods for getting the width, height, and area. The Rectangle class (Child) can now implement the Shape interface (Base). This example now follows the Liskov Substitution Principle (LSP) as it guarantees that the child class, Rectangle of a parent, and Shape can be used in place of their parent without causing any unexpected behavior.
6. DIP – Dependency Inversion Principle
DIP is an acronym for Dependency Inversion Principle. The principle states that high-level classes should not depend on low-level classes, instead, both should depend on Abstractions. In other words, the modules/classes should depend on Abstractions, (interfaces and abstract classes) rather than concrete implementation. By introducing an abstract layer, DIP aims in reducing the coupling between the classes and hence make the application more easier to test and maintain.
Example – 6.1 Before DIP
Java
class Computer {
public void boot() {
System.out.println( "Booting the computer..." );
}
}
class User {
public void startComputer() {
Computer computer = new Computer();
computer.boot();
}
}
public class GFG {
public static void main(String[] args) {
User user = new User();
user.startComputer();
}
}
|
Output
Booting the computer...
In Example 6.1, the `User` class depends on the `Computer` class, which violates the Dependency Inversion Principle (DIP). If there was some change in the Computer class the User class had to be changed as well. This makes it difficult to maintain and manage the code.
6.2 After DIP
Java
interface IComputer {
void boot();
}
class Computer implements IComputer {
public void boot()
{
System.out.println( "Booting the computer..." );
}
}
class User {
public void startComputer(IComputer computer)
{
computer.boot();
}
}
public class GFG {
public static void main(String[] args)
{
User user = new User();
Computer computer = new Computer();
user.startComputer(computer);
}
}
|
Output
Booting the computer...
In Example 6.2, an interface `IComputer` is created, and now, the `User` class depends on the `IComputer` interface instead of the `Computer` class directly. This change allows us to make changes to the Computer class that is an implementation of `IComputer`, as per requirement, without affecting the User class. Hence, the Dependency Inversion Principle allows us to decouple the code and make it more maintainable.
7. COI – Composition Over Inheritance
COI is an acronym for Composition Over Inheritance. As the name implies, this principle emphasizes using Composition instead of Inheritance to achieve code reusability. Inheritance allows a subclass to inherit its superclass’s properties and behavior, but this approach can lead to a rigid class hierarchy that is difficult to modify and maintain. In contrast, Composition enables greater flexibility and modularity in class design by constructing objects from other objects and combining their behaviors. Additionally, the fact that Java doesn’t support multiple inheritances can be another reason to favor Composition over Inheritance.
Example – 7.1 Before COI
Java
class Musician {
public void play() {
System.out.println( "play" );
}
}
class Singer extends Musician {
public void sing() {
System.out.println( "sing" );
}
}
class Drummer extends Musician {
public void drum() {
System.out.println( "drum" );
}
}
|
In Example 7.1, a base class Musician is defined which contains the `play()` method that is common to all musicians. This class is then extended by the `Singer` and `Drummer` classes, which add more methods specific to their functionalities. While this implementation may seem reasonable at first, it can create problems if there is a need to create a new type of Musician who can both sing and play drums. This design can lead to a rigid class hierarchy that becomes difficult to maintain as more functionality is added to the subclasses. This is because inheritance represents an is-a relationship and may not always be the best approach for code reuse.
7.2 After COI
Java
class Singer {
public void sing() {
System.out.println( "sing" );
}
}
class Drummer {
public void drum() {
System.out.println( "drum" );
}
}
class SingerDrummer {
Singer singer = new Singer();
Drummer drummer = new Drummer();
public void play() {
singer.sing();
drummer.drum();
}
}
|
In Example 7.2, the COI principle was applied to implement the same logic using Composition instead of Inheritance. In this approach, the classes for `Singer` and `Drummer` are separated from the Musician class, and their behavior is combined in the `SingerDrummer` class using composition. This provides greater flexibility in class design and modularity by combining objects instead of inheriting their behavior. By using composition, code reusability can be achieved without creating a rigid class hierarchy.
Conclusion
Utilizing Object-Oriented Design Principles when writing code can greatly benefit Java Software Developers. These principles enable the creation of flexible and maintainable code, with low coupling and high cohesion. While applying these principles may require more effort upfront, they can ultimately save time and effort by reducing the number of bugs, improving code readability, and facilitating code reuse.
In this article, we’ve discussed 7 principles: DRY, OCP, SRP, ISP, LSP, DSP, and COI, which provide a framework for writing effective and scalable code. While there may be times when these principles conflict with one another, understanding how to balance them can lead to better overall design choices. It’s also worth noting that these principles are not exhaustive, and there may be additional principles to consider.
Useful Links:
Similar Reads
Open Closed Design Principle in Java
Open Closed Design Principle is one of the most important design principles in the world of software development. It's part of a group of 5 design principles commonly known by the acronym "SOLID" S - Single Responsibility Principle O - Open Closed Principle L - Liskov Substitution Principle I - Inte
12 min read
Learn Java on Your Own in 20 Days - Free!
Indeed, JAVA is one of the most demanding programming languages in the IT world. Statistically, there are around 7-8 million JAVA Developers across the world and the number is growing rapidly. Needless to say, JAVA has a lot of career opportunities in the tech market and the language will undoubtedl
7 min read
Benefits of Learning Multiple Programming Languages in 2025
Programming languages are equipment that is used to create software, websites, and apps. Programmers use distinct languages for different functions. Learning multiple programming languages may be sincerely beneficial. It may be useful to you in lots of terms like getting a job, creating a complex in
8 min read
7 Best Programming Languages For School Students In 2024
Learning programming languages is elementary nowadays. Yes, when it comes to learning a new language from an early age, school students are taking the mantle. It's no surprise that computer science has skyrocketed in recent years as one of the desired fields to study and for the same reason, parents
7 min read
7 Tips to Become a Better Java Programmer in 2025
Java is a very successful and popular programming language. It is very reliable and is widely used in our day-to-day lives, prominently seen in web or mobile applications. There is much demand for Java these days and Java programmers are being recruited largely in the Information Technology sector.
5 min read
Top 10 Reasons to Learn Java in 2025
Java is a general-purpose, object-oriented programming language that was designed by James Gosling at Sun Microsystems in 1991 . The compilation of the Java applications results in the bytecode that can be run on any platform using the Java Virtual Machine. Because of this, Java is also known as a W
7 min read
Java OOP(Object Oriented Programming) Concepts
Java Object-Oriented Programming (OOPs) is a fundamental concept in Java that every developer must understand. It allows developers to structure code using classes and objects, making it more modular, reusable, and scalable. The core idea of OOPs is to bind data and the functions that operate on it,
13 min read
Top 10 Object-Oriented Programming Languages in 2024
In the present world, almost all sectors make use of software applications or computer programs that require to be coded. Programming Language is a set of instructions used in specific notations to write computer programs. It, basically tells the computer what to do. All Programming languages are no
9 min read
Open Closed Principle in Java with Examples
In software development, the use of object-oriented design is crucial. It helps to write flexible, scalable, and reusable code. It is recommended that the developers follow SOLID principles when writing a code. One of the five SOLID principles is the open/closed principle. The principle states that
9 min read
12 Tips to Optimize Java Code Performance
While working on any Java application we come across the concept of optimization. It is necessary that the code which we are writing is not only clean, and without defects but also optimized i.e. the time taken by the code to execute should be within intended limits. In order to achieve this, we nee
8 min read