Skip to main content
Java · lesson 11 of 66
Open the Java page →
Java beginner Lesson 11 of 66

Java OOP — Complete Guide

A map of Java OOP from classes and objects through the four pillars to SOLID and design patterns, with one runnable program showing every pillar.

By SkillByExample · Updated

Java is a class-based, object-oriented language. Every piece of running logic lives inside a class, and every value you work with (except the 8 primitives) is an object. Understanding OOP is not optional in Java — it is the language. The sooner you build a solid mental model of objects, classes, and the four pillars, the easier every other topic becomes.

What Is OOP?

Object-Oriented Programming organises code around objects — self-contained units that bundle data (fields) and behaviour (methods). This mirrors how we think about the real world: a BankAccount knows its own balance and knows how to deposit or withdraw. A Car knows its own speed and knows how to accelerate. This bundling of state and behaviour in one place is what makes large codebases manageable — each object is responsible for itself.

The alternative — procedural code — passes data between separate functions. OOP keeps data and the functions that operate on it together, making large codebases dramatically easier to manage.

The Four Pillars

Each pillar solves a specific design problem. Encapsulation prevents accidental state corruption. Inheritance enables code reuse across related types. Polymorphism lets one piece of code work with many different types. Abstraction lets you work at a higher level without being tangled in implementation details.

PillarWhat it meansJava mechanism
EncapsulationHide internal state, expose a clean interfaceprivate fields + getters/setters
InheritanceChild classes reuse and extend parent classesextends keyword
PolymorphismOne interface, many implementationsMethod overriding + @Override
AbstractionWork with concepts, not implementation detailsabstract classes + interface

Learning Path

Work through these in order. Each page builds on the previous one.

Beginner

  1. Introduction to OOP in Java — classes, objects, constructors, this
  2. Encapsulation — access modifiers, getters/setters, immutability
  3. Inheritance — extends, super, method overriding, final
  4. Polymorphism — runtime dispatch, @Override, interface polymorphism
  5. Abstraction — abstract classes, interface, default methods

Intermediate

  1. Interfaces — Deep Dive — segregation, default methods, functional interfaces
  2. Abstract Classes — template method pattern, partial implementations
  3. Method Overloading — compile-time polymorphism, varargs, autoboxing
  4. Composition vs Inheritance — when to favour composition, Decorator pattern

Advanced

  1. SOLID Principles — five design principles every Java engineer must know
  2. Design Patterns — Singleton, Factory, Observer, Strategy, Builder
  3. Generics and OOP — type-safe collections, bounded wildcards, generic methods

The four pillars in one program

Here is a small but complete program that uses all four pillars at once. Each comment names the pillar at work; the lessons above cover each in depth.

import java.util.List;

public class Pillars {
    interface Shape {                                   // abstraction: what, not how
        double area();
        default String describe() { return getClass().getSimpleName() + " " + String.format("%.1f", area()); }
    }

    static class Rectangle implements Shape {
        private final double w, h;                      // encapsulation: private state
        Rectangle(double w, double h) {
            if (w <= 0 || h <= 0) throw new IllegalArgumentException("sides must be positive");
            this.w = w; this.h = h;
        }
        public double area() { return w * h; }
    }

    static class Square extends Rectangle {             // inheritance: a Square is a Rectangle
        Square(double side) { super(side, side); }
    }

    record Circle(double r) implements Shape {
        public double area() { return Math.PI * r * r; }
    }

    public static void main(String[] args) {
        List<Shape> shapes = List.of(new Rectangle(2, 3), new Square(4), new Circle(1));
        for (Shape s : shapes) System.out.println(s.describe());   // polymorphism
        double total = shapes.stream().mapToDouble(Shape::area).sum();
        System.out.printf("total %.2f%n", total);
    }
}
$ java Pillars.java
Rectangle 6.0
Square 16.0
Circle 3.1
total 25.14

The loop never asks which kind of shape it has: each object answers area() its own way (polymorphism) behind the Shape interface (abstraction). Square reuses Rectangle’s fields and validation through super (inheritance), and nothing outside Rectangle can change w or h (encapsulation).

Practice

  1. Add a Triangle(double base, double height) record implementing Shape and include it in the list. Which existing code had to change?
Solution
import java.util.List;

public class AddTriangle {
    interface Shape {
        double area();
        default String describe() { return getClass().getSimpleName() + " " + String.format("%.1f", area()); }
    }

    record Circle(double r) implements Shape {
        public double area() { return Math.PI * r * r; }
    }

    record Triangle(double base, double height) implements Shape {
        public double area() { return 0.5 * base * height; }
    }

    public static void main(String[] args) {
        List<Shape> shapes = List.of(new Circle(1), new Triangle(3, 4));
        for (Shape s : shapes) System.out.println(s.describe());
    }
}
$ java AddTriangle.java
Circle 3.1
Triangle 6.0

Only the list changed. The loop and describe work with any Shape, which is the payoff of polymorphism: new types plug in without editing the code that uses them.

  1. Try to construct new Rectangle(-1, 2), catch the exception, and print its message. Which pillar protects the object here?
Solution
public class Guarded {
    static class Rectangle {
        private final double w, h;
        Rectangle(double w, double h) {
            if (w <= 0 || h <= 0) throw new IllegalArgumentException("sides must be positive");
            this.w = w; this.h = h;
        }
    }

    public static void main(String[] args) {
        try {
            new Rectangle(-1, 2);
        } catch (IllegalArgumentException e) {
            System.out.println("rejected: " + e.getMessage());
        }
    }
}
$ java Guarded.java
rejected: sides must be positive

Encapsulation: with private final fields and a validating constructor, no invalid Rectangle can ever exist.

  1. Override describe() in Square so it prints Square of side 4.0 instead of the default, and check that the loop picks it up without changes.
Solution
import java.util.List;

public class OverrideDescribe {
    interface Shape {
        double area();
        default String describe() { return getClass().getSimpleName() + " " + area(); }
    }

    static class Rectangle implements Shape {
        protected final double w, h;
        Rectangle(double w, double h) { this.w = w; this.h = h; }
        public double area() { return w * h; }
    }

    static class Square extends Rectangle {
        Square(double side) { super(side, side); }
        @Override public String describe() { return "Square of side " + w; }
    }

    public static void main(String[] args) {
        for (Shape s : List.of(new Rectangle(2, 3), new Square(4))) System.out.println(s.describe());
    }
}
$ java OverrideDescribe.java
Rectangle 6.0
Square of side 4.0

The call site is unchanged; the JVM picks Square’s override at runtime because that is the object’s actual class.

Frequently Asked Questions

What are the four pillars of OOP in Java?
Encapsulation hides an object's state behind methods, usually with private fields. Inheritance lets a class extend another and reuse its code. Polymorphism lets one call run different code depending on the object's class. Abstraction lets code depend on an interface or abstract class rather than on implementation details.
Do I need to learn all OOP concepts before writing Java?
No. Start with classes, objects and encapsulation, which you need for almost every program. Add inheritance and polymorphism once you are comfortable, and pick up interfaces, abstract classes and design patterns as your programs grow.
Should I prefer inheritance or composition in Java?
Prefer composition, where a class holds other objects and delegates to them, unless the relationship is a genuine is-a with a stable parent. Inheritance couples the subclass to the parent's implementation, so changes to the parent can break subclasses. Interfaces give polymorphism without that coupling.
Is Java a purely object-oriented language?
Not purely. Java's eight primitive types such as int and boolean are not objects, and static methods run without any object. Every method still lives inside a class, though, and everything other than primitives is an object reached through a reference.