Java 4 min read

Understanding Final Classes in Java

Learn when and why to use final classes in Java. Understand the design principle of prohibiting inheritance for better code safety.

MR

Moshiour Rahman

Advertisement

What is a Final Class?

A final class is simply a class that cannot be extended. Any attempt to subclass it results in a compile-time error.

final class Owl {
    public void hoot() {
        System.out.println("Hoot!");
    }
}

// Compilation Error: Cannot extend final class 'Owl'
class TechyOwl extends Owl {
}

Common Misconceptions

A final class does NOT mean:

  • All references to objects of this class are final
  • All fields in the class are automatically final
  • The class is immutable

A final class can still have mutable fields:

final class Counter {
    private int count = 0;  // Mutable field

    public void increment() {
        count++;  // State can change
    }
}

When Should You Use Final Classes?

Preventing Illogical Inheritance

Consider this problematic code:

public class Car {
    public void move() {
        System.out.println("Driving on road");
    }
}

// This compiles but makes no sense!
public class Airplane extends Car {
    @Override
    public void move() {
        System.out.println("Flying in air");
    }
}

An Airplane should not extend Car - they’re fundamentally different concepts. Using final prevents such design mistakes:

public final class Car {
    public void move() {
        System.out.println("Driving on road");
    }
}

Joshua Bloch’s Guidance

In Effective Java, Joshua Bloch advises:

Design and document for inheritance or else prohibit it.

The interaction between subclasses and parent classes can be unpredictable if the parent wasn’t designed for inheritance.

Classes Should Be One of Two Types

  1. Classes designed for extension - Well-documented with clear extension points
  2. Final classes - Explicitly prohibit inheritance

Real-World Examples

Java Standard Library

Many core Java classes are final:

// All of these are final classes
String text = "Hello";
Integer number = 42;
Long bigNumber = 100L;

Why? Because these classes are immutable and their behavior must be guaranteed.

Security-Sensitive Classes

public final class SecurityValidator {
    public boolean validateToken(String token) {
        // Critical security logic
        return isValid(token);
    }
}

Making security classes final prevents attackers from creating malicious subclasses that bypass security checks.

Final vs Sealed Classes (Java 17+)

Java 17 introduced sealed classes for more granular control:

// Only specific classes can extend
public sealed class Vehicle permits Car, Motorcycle, Truck {
    // ...
}

public final class Car extends Vehicle { }
public final class Motorcycle extends Vehicle { }
public final class Truck extends Vehicle { }

This provides a middle ground between open inheritance and complete prohibition.

Best Practices

When to Use Final

  1. Utility classes - Classes with only static methods
  2. Immutable classes - Value objects that shouldn’t change
  3. Security-critical classes - Prevent tampering via subclassing
  4. Classes not designed for extension - The default choice

When NOT to Use Final

  1. Framework base classes - Like Spring’s Controller classes
  2. Template pattern implementations - Where subclassing is the design
  3. Test doubles - Mocking frameworks need to extend classes

The Composition Principle

Using final also supports the principle of favoring composition over inheritance:

// Instead of extending
public final class EnhancedList<E> {
    private final List<E> delegate;

    public EnhancedList(List<E> delegate) {
        this.delegate = delegate;
    }

    public void add(E element) {
        delegate.add(element);
    }

    // Add new behavior without inheritance
    public E getLastElement() {
        return delegate.get(delegate.size() - 1);
    }
}

Conclusion

The final keyword for classes is a powerful design tool:

  • Five characters that prevent future maintenance headaches
  • Signals that the class wasn’t designed with inheritance in mind
  • Can always be removed later if needed (but harder to add)
  • Supports composition over inheritance

Key Takeaways:

  • Make classes final by default unless you explicitly design for extension
  • Document extension points if you do allow inheritance
  • Use sealed classes in Java 17+ for controlled hierarchies
  • Consider composition as an alternative to inheritance

Advertisement

MR

Moshiour Rahman

Software Architect & AI Engineer

Share:
MR

Moshiour Rahman

Software Architect & AI Engineer

Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.

Related Articles

Comments

Comments are powered by GitHub Discussions.

Configure Giscus at giscus.app to enable comments.