Should you use Enums with JPA?

blog-post-img

With JPA it’s possible to map Java enums to columns in a database table using the Enumerated annotation.

@Enumerated
private Color color;    

An enum can be mapped as an integer or a string but mapping of enums that contain state is not supported.

EnumType.ORDINAL

The default mapping is using an integer that represents the ordinal value of the enum value. The ordinal value is created at compile time based on the position of the enum value.

Let’s have a look at an example enum Color

public enum Color {
    RED, GREEN, BLUE
}

Enum is a Java compile time construct that means the enum is translated in a class.

This is how Color looks after decompiling:

public final class Color extends Enum {

    public static Color[] values() {
        return (Color[])$VALUES.clone();
    }

    public static Color valueOf(String name) {
        return (Color)Enum.valueOf(com/example/demo/Color, name);
    }

    private Color(String s, int i) {
        super(s, i);
    }

    public static final Color RED;
    public static final Color GREEN;
    public static final Color BLUE;
    private static final Color $VALUES[];

    static {
        RED = new Color("RED", 0);
        GREEN = new Color("GREEN", 1);
        BLUE = new Color("BLUE", 2);
        $VALUES = (new Color[] {
            RED, GREEN, BLUE
        });
    }
}

As you can see RED has the value 0, GREEN 1 and BLUE 2.

But imagine what will happen if we change the enum Color like this:

public enum Color {
    YELLOW, RED, GREEN, BLUE
}    

Now the ordinal values are all changed! This means you have to migrate your database and if you don’t do this you will have wrong values!

To conclude using the ordinal value is a dangerous idea and I don’t understand why the JPA expert group decided to define this as the default behavior.

So how to solve this issue?

EnumType.STRING

We can define the mapping to use the string representation of the enum instead of the ordinal value:

@Enumerated(EnumType.STRING)
private Color color;

Like this JPA will store RED, GREEN and BLUE in the column of the database table and this resolves the problem when adding new enum values or changing the order of existing ones.

But if we change the name of the enum value itself we still have to do a database migration.

The problem with enums is that there is no representation of the allowed values in the database. So your application is responsible to only allow valid values to be inserted in the database table.

But what happen when someone is adding a value directly in the database table that is not valid? For example I added a record in the table with the color BLACK and now Hibernate is complaining:

java.lang.IllegalArgumentException: Unknown name value [BLACK] for enum class [Color]

To avoid this problem we could add a check constraint to the database table:

CREATE TABLE Cars (
    color VARCHAR(20)
    CHECK (color IN ('RED', 'GREEN', 'BLUE'))
);

This would solve the problem but you see what happened? We have code duplication!

RED, GREEN and BLUE are defined in the enum and in the database as well and now we have to make sure to always change both artifacts the enum and the table.

Are Enums with JPA an Antipattern?

Bill Karwin wrote in his highly recommended book SQL Antipatterns: Avoiding the Pitfalls of Database Programming in “Chapter 11.31 Flavors” about the same problem when using enum like types in database tables.

I fully agree with his final statement:

Use metadata (in our case enums) when validating against a fixed set of values (that never change).

Use data when validating against a fluid set of values.

Think twice when you use an enum with JPA.

Simon Martinelli
Follow Me