1. Introduction

In this article, we’re going to learn about using constants in Java with a focus on common patterns and anti-patterns.

We’ll start with some basic conventions for defining constants. From there, we’ll move onto common anti-patterns before finishing with a look at common patterns.

2. Basics

A constant is a variable whose value won’t change after it’s been defined.

Let’s look at the basics for defining a constant:

private static final int OUR_CONSTANT = 1;

Some of the patterns we’ll look at will address the public or private access modifier decision. We make our constants static and final and give them an appropriate type, whether that’s a Java primitive, a class, or an enum. The name should be all capital letters with the words separated by underscores, sometimes known as screaming snake case. Finally, we provide the value itself.

3. Anti-Patterns

First, let’s start by learning what not to do. Let’s look at a couple of common anti-patterns we might encounter when working with Java constants.

3.1. Magic Numbers

Magic numbers are are numeric literals in a block of code:

if (number == 3.14159265359) {
    // ...
}

They’re hard for other developers to understand. Additionally, if we’re using a number throughout our code, it’s difficult to deal with changing the value. We should instead define the number as a constant.

3.2. A Large Global Constants Class

When we start a project, it might feel natural to create a class named Constants or Utils with the intention of defining all the constants for the application there. For smaller projects, this might be ok, but let’s consider a couple of reasons why this isn’t an ideal solution.

First, let’s imagine we have a hundred or more constants all in our constants class. If the class isn’t maintained, both to keep up with documentation and to occasionally refactor the constants into logical groupings, it’s going to get pretty unreadable. We could even end up with duplicate constants with slightly different names. This approach is likely to give us readability and maintainability problems in anything but the smallest projects.

In addition to the logistics of maintaining the Constants class itself, we’re also inviting other maintainability problems by encouraging too much interdependency with this one global constants class and various other parts of our application.

On a more technical side, the Java compiler places the value of the constant into referencing variables in the classes in which we use them. So, if we change one of our constants in our constants class and only recompile that class and not the referencing class, we can get inconsistent constant values.

3.3. The Constant Interface Anti-Pattern

The constant interface pattern is when we define an interface that contains all of the constants for certain functionality and then have the classes that need those functionalities to implement the interface.

Let’s define a constant interface for a calculator:

public interface CalculatorConstants {
    double PI = 3.14159265359;
    double UPPER_LIMIT = 0x1.fffffffffffffP+1023;
    enum Operation {ADD, SUBTRACT, MULTIPLY, DIVIDE};
}

Next, we’ll implement our CalculatorConstants interface:

public class GeometryCalculator implements CalculatorConstants {    
    public double operateOnTwoNumbers(double numberOne, double numberTwo, Operation operation) {
       // Code to do an operation
    }
}

The first argument against using a constant interface is that it goes against the purpose of an interface. We’re meant to use interfaces to create a contract for the behavior our implementing classes are going to provide. When we create an interface full of constants, we’re not defining any behavior.

Secondly, using a constant interface opens us up to run-time issues caused by field shadowing. Let’s look at how that might happen by defining a UPPER_LIMIT constant within our GeometryCalculator class:

public static final double UPPER_LIMIT = 100000000000000000000.0;

Once we define that constant in our GeometryCalculator class, we hide the value in the CalculatorConstants interface for our class. We could then get unexpected results.

Another argument against this anti-pattern is that it causes namespace pollution. Our CalculatorConstants will now be in the namespace for any of our classes that implement the interface as well as any of their subclasses.

4. Patterns

Earlier, we looked at the appropriate form for defining constants. Let’s look at some other good practices for defining constants within our applications.

4.1. General Good Practices

If constants are logically related to a class, we can just define them there. If we view a set of constants as members of an enumerated type, we can use an enum to define them.

Let’s define some constants in a Calculator class:

public class Calculator {
    public static final double PI = 3.14159265359;
    private static final double UPPER_LIMIT = 0x1.fffffffffffffP+1023;
    public enum Operation {
        ADD,
        SUBTRACT,
        DIVIDE,
        MULTIPLY
    }

    public double operateOnTwoNumbers(double numberOne, double numberTwo, Operation operation) {
        if (numberOne > UPPER_LIMIT) {
            throw new IllegalArgumentException("'numberOne' is too large");
        }
        if (numberTwo > UPPER_LIMIT) {
            throw new IllegalArgumentException("'numberTwo' is too large");
        }
        double answer = 0;
        
        switch(operation) {
            case ADD:
                answer = numberOne + numberTwo;
                break;
            case SUBTRACT:
                answer = numberOne - numberTwo;
                break;
            case DIVIDE:
                answer = numberOne / numberTwo;
                break;
            case MULTIPLY:
                answer = numberOne * numberTwo;
                break;
        }
        
        return answer;
    }
}

In our example, we’ve defined a constant for UPPER_LIMIT that we’re only planning on using in the Calculator class, so we’ve set it to private. We want other classes to be able to use PI and the Operation enum, so we’ve set those to public.

Let’s consider some of the advantages of using an enum for Operation. The first advantage is that it limits the possible values. Imagine that our method takes a string for the operation value with the expectation that one of four constant strings is supplied. We can easily foresee a scenario where a developer calling the method sends their own string value. With the enum, the values are limited to those we define. We can also see that enums are especially well suited to use in switch statements.

4.2. Constants Class

Now that we’ve looked at some general good practices, let’s consider the case when a constants class might be a good idea. Let’s imagine our application contains a package of classes that need to do various kinds of mathematical calculations. In this case, it probably makes sense for us to define a constants class in that package for constants that we’ll use in our calculations classes.

Let’s create a MathConstants class:

public final class MathConstants {
    public static final double PI = 3.14159265359;
    static final double GOLDEN_RATIO = 1.6180;
    static final double GRAVITATIONAL_ACCELERATION = 9.8;
    static final double EULERS_NUMBER = 2.7182818284590452353602874713527;
    
    public enum Operation {
        ADD,
        SUBTRACT,
        DIVIDE,
        MULTIPLY
    }
    
    private MathConstants() {
        
    }
}

The first thing we should notice is that our class is final to prevent it from being extended. Additionally, we’ve defined a private constructor so it can’t be instantiated. Finally, we can see that we’ve applied the other good practices we discussed earlier in the article. Our constant PI is public because we anticipate needing to access it outside of our package. The other constants we’ve left as package-private, so we can access them within our package. We’ve made all of our constants static and final and named them in a screaming snake case. The operations are a specific set of values, so we’ve used an enum to define them.

We can see that our specific package-level constants class is different from a large global constants class because it’s localized to our package and contains constants relevant to that package’s classes.

5. Conclusion

In this article, we considered the pros and cons of some of the most popular patterns and anti-patterns seen when using constants in Java. We started out with some basic formatting rules, before covering anti-patterns. After learning about a couple of common anti-patterns, we looked at patterns that we often see applied to constants.

As always the code is available over on GitHub.