Interfaces Should Only Be Used to Define Types

In Java, the principle that "interfaces should only be used to define types" means interfaces should contain only abstract methods and constants, without any concrete implementation. An interface is a specification or contract that dictates a set of method signatures and behaviors that implementing classes must follow.

Let's look at a simple example:

public interface Animal {
    void makeSound();
    int MAX_AGE = 100;
}

public class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

public class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow!");
    }
}

In this example, the Animal interface defines an abstract method makeSound() and a constant MAX_AGE. The Dog and Cat classes implement the Animal interface and provide concrete implementations of makeSound(). Thus, the Animal interface is used solely to define a type, while the implementation details are left to the implementing classes.

When a class implements an interface, the interface serves as a type that can reference instances of that class. Therefore, implementing an interface indicates that clients can perform certain actions (defined by the interface's methods) on instances of that class. Using interfaces for any other purpose is inpapropriate.

There is a type of interface known as a constant interface, which does not meet the above criteria. Such interfaces contain no methods; they only include static final fields, each exporting a constant. Classes that use these constants implement the interface to avoid qualifying the constant names with the class name. Here is an example:

// Anti-pattern: do not use
public interface PhysicalConstants {
    static final double AVOGADROS_NUMBER = 6.02214199e23;
    static final double BOLTZMANN_CONSTANT = 1.3806503e-23;
    static final double ELECTRON_MASS = 9.109_383_56e-31;
}

public class JJChild implements PhysicalConstants {
    public static void main(String[] args) {
        // Anti-pattern: do not use
        double avogadrosNumber = JJChild.AVOGADROS_NUMBER;
    }
}

Constant interfaces are a poor use of interfaces. Using constants internally in a class is an implementation detail. Implementing a constant interface leaks this implementation detail into the class's exported API. Implementing a constant interface is of no value to users of the class. In fact, it often confuses them. Worse, it represents a commitment: if in a future release the class is modified to no longer need those constants, it must still implement the interface to maintain binary compatibility. If a non-final class implements a constant interface, all its subclasses will also have their namespaces polluted by the constants.

The Java platform libraries contain several constant interfaces, such as java.io.ObjectStreamConstants. These should be considered negative examples and not emulated.

To export constants, there are several reasonable alternatives:

  • If the constants are closely related to an existing class or interface, add them to that class or interface. For example, the numeric wrapper classes like Integer and Double export MIN_VALUE and MAX_VALUE constants.
  • If the constants are best viewed as members of an enumerated type, export them using an enum type (see Item 30).
  • Otherwise, export the constants using a non-instantiable utility class (see Item 4).

Example of a utility class:

public class PhysicalConstants {
    private PhysicalConstants() {} // Prevents instantiation

    public static final double AVOGADROS_NUMBER = 6.02214199e23;
    public static final double BOLTZMANN_CONSTANT = 1.3806503e-23;
    public static final double ELECTRON_MASS = 9.109_383_56e-31;
}

public class JJChild {
    public static void main(String[] args) {
        // Recommended usage
        double boltzmannConstant = PhysicalConstants.BOLTZMANN_CONSTANT;
        System.out.println(boltzmannConstant);
    }
}

Using a utility class requires qualifying constant names with the class name. If you want to avoid that, you can use static imports:

import static com.example.MathUtils.*;

public class Main {
    public static void main(String[] args) {
        int sum = add(3, 4);
        System.out.println("The sum is: " + sum);
    }
}

In summary, interfaces should only be used to define types; they should not be used to export constants.

Tags: java Interface design principles constants anti-pattern

Posted on Sat, 09 May 2026 17:54:01 +0000 by snake310