Not Every Class Should Be Instantiated

Classes that contain only static methods and fields are useful. You can group primitive- or array-related methods in classes like java.lang.Math and java.util.Array, or expose factory methods like java.util.Collections that create implementations of interfaces.

The same applies to methods tied to a final class, because it cannot be extended.


How Do You Prevent Instantiation?

Assume you have a utility class like this. It does not need instances, so it has no explicit constructor. It only contains static methods, and the name emphasizes that it is a utility class.

public class DateUtility {
    private static String FULL_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";

    // no constructor

    public static String convertDateToString(Date date) {
        return new SimpleDateFormat(FULL_DATE_FORMAT).format(date);
    }
}

If you do not declare a constructor, the compiler generates a default one. So even though the class is meant to be used statically, someone can still instantiate it.

public void someMethod() {
    // expected usage
    DateUtility.convertDateToString(new Date());
    
    // but someone can still do this
    DateUtility dateUtility = new DateUtility();
    String formattedToday = dateUtility.convertDateToString(new Date());
}

Making the class abstract does not solve this. A subclass can still be instantiated.

abstract class DateUtility {
    // ... omitted
}

class SubDateUtility extends DateUtility {
    // ... omitted
}

public class PrivateConstructorTest {
    public static void main(String[] args) {
        // abstract classes cannot be instantiated
        // DateUtility dateUtility = new DateUtility();

        // okay
        SubDateUtility subDateUtility = new SubDateUtility();
    }
}

The fix is simple: add a private constructor.

class DateUtility {
    private DateUtility() {
        /**
         * Block usage inside the class as well.
         */
        throw new AssertionError();
    }

    // omitted
}

public class PrivateConstructorTest {
    public static void main(String[] args) {
        // DateUtility() has private access in DateUtility
        DateUtility dateUtility = new DateUtility();
    }
}

The AssertionError prevents accidental construction even inside the class. With no accessible constructors, the class cannot be extended either.

One more detail: if there is another public constructor with different parameters and the subclass defines a matching constructor, inheritance is still possible even if the no-arg constructor is private.

class DateUtility {
    private DateUtility() {
        throw new AssertionError();
    }

    public DateUtility(int val) {
        //
    }
}

class SubDateUtility extends DateUtility {
    public SubDateUtility(int val) {
        // must call the matching constructor in the superclass
        super(val);
    }
}