Ensuring completeness of switch statements

It is good practice to ensure that each switch statement should be complete in the sense that each execution at run time will find a suitable branch. This is actually a non-trivial task when taking all of the following into consideration:

Additionally, the JLS mandates a flow analysis for enum switch statements which can lead to unexpected compile time errors:

    enum Colors { RED, GREEN, BLUE }
    String colorString(Color c) {
        switch(c) {
            case RED: return "red";
            case GREEN: return "green";
            case BLUE: return "blue";
        }
    }

The compiler will answer:

"This method must return a result of type String. Note that a problem regarding missing 'default:' on 'switch' has been suppressed, which is perhaps related to this problem".

This message has been designed to alert users of the different notions of completeness: the flow analysis mandated by the JLS considers each enum switch statement without a default case as incomplete, even if it lists all (currently known) enum constants. This concerns return statements as well as definite assignment of local variables or blank final fields. However, if you follow all recommendations below, the above error message will never occur.

Please consult the compiler preferences regarding the individual configuration options for reporting various levels of incompleteness. In the sequel we discuss different design goals and policies and how they can be checked using the Eclipse Java compiler.

On using "default:"

Optimally, each switch statement should have a meaningful default branch, which handles all cases that are not explicitly listed using an "always-reasonable" strategy. Obviously, such a strategy is difficult or even impossible to find in many cases, but if you can find a meaningful default implementation, that's certainly the best solution.

Recommendation: By letting the compiler warn you about each switch statement lacking a default case you are reminded to search for a good default implementation for every switch statement.

If the compiler warns you about a missing default case, but the reasonable thing to do by default is just do nothing, this might as well be worth documenting.

Recommendation: If "do nothing" is a reasonable default strategy you should add a default: label whose action is only a comment explaining that (and why) doing nothing is OK in this switch. By doing so you also tell the compiler that you did not simply forget about a default case.

If definitely no reasonable default implementation can be found, as a last resort you may want to prevent an unexpected value to be the root cause for other errors further downstream, i.e., you may want to fail early at runtime.

Recommendation: If neither a reasonable default implementation can be found nor "do nothing" appears to be a good strategy, add a default: case that throws an exception and/or logs the problem.

Each switch statement should be assignable to one of the three above categories. This means letting the compiler warn you about each missing default case is a universally valid strategy.

Handling all enum constants

When performing a switch over an enum value, it may be desirable to explicitly cover each enum constant by a corresponding case statement.

Recommendation: If you want to be warned about enum switch statements that lack a case statement for any of the enum constants, please consider enabling the option Signal even if 'default' case exists.

If you don't like the option that all enum switches should mention all enum constants, but still want to get the warning about missing case labels for some enum switch statements, you'll need to select the strategy for each individual enum switch statement.

Recommendation: If the above warning is desirable for some switch statements, consider to globally enable the warning, and identify those switch statements where it is OK to omit cases for some enum constants, because those are suitably handled by the default case. For these switch statements use the '//$CASES-OMITTED$' tag comment to document your decision:
    String colorString2(Color c) {
        switch(c) {
        case RED: return "red";
        case GREEN: return "green";
            //$CASES-OMITTED$
        default:
        	return "unknown color";
        }
    }

Hint: If the compiler reports something like "The enum constant BLUE should have a corresponding case label in this enum switch on Color" a quick fix will be offered for inserting the tag comment.

Summary

The above considerations have shown that you get most help from the compiler if you enable all optional warnings regarding incomplete switch statements, and use empty documented default cases plus //$CASES-OMITTED$ tag comments to mark those switch statements where incompleteness is OK by design. If you follow these recommendations, you will get all relevant warnings (or errors if you like) when branches are accidentally omitted, including the situation of late addition of one or more enum constants. All exceptions from this maximum completeness will be documented in the code and no longer flagged with a warning.

Still the compiler cannot prevent the use of inconsistent class versions at runtime, so a class may be compiled against an old version of an enum type, but in the running application the enum type may have more enum constants. For these and similar situations an appropriate "catch-all" default implementations can raise runtime exceptions to alert of this inconsistency and prevent the error from propagating further into the application.

As an added value of the strict compiler settings recommended above, the dubious error message about a missing return statement shown above (or similar messages about uninitialized variables) will no longer occur.