57

Normally, default is not necessary in a switch statement. However, in the following situation the code successfully compiles only when I uncomment the default statement. Can anybody explain why?

public enum XYZ {A,B};
public static String testSwitch(XYZ xyz)
{
    switch(xyz)
    {
    case A:
        return "A";
    case B:
    //default:
        return "B";
    }
}
Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
apoorv020
  • 5,420
  • 11
  • 40
  • 63

8 Answers8

58

The reason that you have to uncomment the default is that your function says that it returns a String, but if you only have case labels defined for A and B then the function will not return a value if you pass in anything else. Java requires that all functions that state that they return a value actually return a value on all possible control paths, and in your case the compiler isn't convinced that all possible inputs have a value returned.

I believe (and I'm not sure of this) that the reason for this is that even if you cover all your enum cases, the code could still fail in some cases. In particular, suppose that you compile the Java code containing this switch statement (which works just fine), then later on change the enum so that there's now a third constant - let's say C - but you don't recompile the code with the switch statement in it. Now, if you try writing Java code that uses the previously-compiled class and passes in C into this statement, then the code won't have a value to return, violating the Java contract that all functions always return values.

More technically speaking, I think the real reason is that the JVM bytecode verifier always rejects functions in which there is some control path that falls off the end of a function (see §4.9.2 of the JVM spec), and so if the code were to compile it would just get rejected by the JVM at runtime anyway. The compiler therefore gives you the error to report that a problem exists.

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • 2
    @apoorv020, I dont think that this is stupidity. There are always reasons because this optimization is obvious. – Alex Nikolaenkov Feb 16 '11 at 06:45
  • 3
    @apoorv020- I just updated my answer with a pretty logical reason that the compiler would reject this. In general compilers aren't stupid - there are some really smart people who pour their heart and soul into making them more and more awesome - and so there's probably a reason for most design decisions in what the compiler allows. – templatetypedef Feb 16 '11 at 06:47
  • 2
    @templatetypedef, what is your approach to handle this "default" situation? Myself I never provide `default` and throw an `IllegalArgumentException` in the end of such methods. – Alex Nikolaenkov Feb 16 '11 at 06:51
  • @Alex Nikolaenkov- I usually have a `default` label that throws an error or asserts false. It's essentially the same approach you're taking, with the logic in a different place. FindBugs is usually good about warning if you miss something from a `switch`, so this is usually fine. – templatetypedef Feb 16 '11 at 06:54
  • 1
    @templatetypedef - But the argument about source files being out of sync with each other is very generic, why does it make a difference only in the case of switch and enum? What about if I change class definitions? Can you point me to a link which covers VM behavior such cases? – apoorv020 Feb 16 '11 at 06:59
  • 1
    @apoorv020- Sure! There's actually a family of errors rooted at `IncompatibleClassChangeError` that are errors that come up in this case. See section 12.7.3 of http://java.sun.com/docs/books/jvms/second_edition/html/Concepts.doc.html#22574 and specifically the part about verification. – templatetypedef Feb 16 '11 at 07:02
  • 4
    @apoorv020 - The compiler is not stupid. It is actually treating this as an error because it is *required to* by the JLS. See my answer for an explanation. – Stephen C Jul 23 '13 at 13:01
  • @templatetypedef not sure why this needs a complicated explanation. The reason you need the default is because you can call testSwitch(null) in which case there wouldn't be a return statement unless you cover the default case. – EasterBunnyBugSmasher Nov 29 '20 at 11:17
  • 3
    @EasterBunnyBugSmasher Things may have changed since I last did major Java programming, but [it used to be the case that if you did a switch on a `null` `enum` it would trigger a `NullPointerException` rather than considering any `case` labels](https://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.11). If that’s still the case, then the OP’s code would trigger an exception rather than reaching the end of the function without returning. – templatetypedef Nov 29 '20 at 16:38
52

I think this is explained by the JLS definite assignment rules for switch statements (JLS 16.2.9) which states the following:

"V is [un]assigned after a switch statement iff all of the following are true:

  • Either there is a default label in the switch block or V is [un]assigned after the switch expression.

If we then apply this to the notional V which is the return value of the method, we can see that if there is no default branch, the value would be notionally unassigned.

OK ... I'm extrapolating definite assignment rules to cover return values, and maybe they don't. But the fact that I couldn't find something more direct in the spec doesn't mean it isn't there :-)


There's another (more sound) reason why the compiler has to give an error. It stems from the binary compatibility rules for enum (JLS 13.4.26) which state the following:

"Adding or reordering constants from an enum type will not break compatibility with pre-existing binaries."

So how does that apply in this case? Well suppose that the compiler was allowed to infer that the OP's example switch statement always returned something. What happens if the programmer now changes the enum to add an extra constant? According to the JLS binary compatibility rules, we haven't broken binary compatibility. Yet the method containing the switch statement can now (depending on its argument) return an undefined value. That cannot be allowed to happen, so therefore the switch must be a compilation error.


In Java 12 they have introduced enhancements to switch that include switch expressions. This runs into the same problem with enums that change between compile time and runtime. According to the JEP 354, they solving this problem as follows:

The cases of a switch expression must be exhaustive; for all possible values there must be a matching switch label. (Obviously switch statements are not required to be exhaustive.)

In practice this normally means that a default clause is required; however, in the case of an enum switch expression that covers all known constants, a default clause is inserted by the compiler to indicate that the enum definition has changed between compile-time and runtime. Relying on this implicit default clause insertion makes for more robust code; now when code is recompiled, the compiler checks that all cases are explicitly handled. Had the developer inserted an explicit default clause (as is the case today) a possible error will have been hidden.

The only thing that is not crystal clear is what the implicit default clause would actually do. My guess is that it would throw an unchecked exception. (As of right now, the JLS for Java 12 has not been updated to describe the new switch expressions.)

Community
  • 1
  • 1
Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • 9
    Interesting. So in essence this is a flaw in the spec, not the compiler. Because of this detail, what could easily be a compile-time error (forgetting an enum value in a switch statement) becomes a runtime error. – augurar Jul 30 '14 at 23:39
  • 3
    @augurar - I don't understand why you call that a flaw in the spec. It is fundamental to the nature of Java that different classes can be compiled at different times. The only way to avoid the (hypothetical) binary compatibility issue with enum and switches would be to *require* that dependent classes are recompiled after the classes they depend on. That would be a major, breaking change ... and would not be acceptable. – Stephen C Jul 31 '14 at 02:41
  • 1
    I'm with @aurugar: adding an enum constant will often break behaviour, so making it hard to catch these at compile time feels like an oversight in the spec. FWIW the suggestion at http://stackoverflow.com/questions/16797529/how-to-ensure-completeness-in-an-enum-switch-at-compile-time allows a reasonable workaround (if slightly longwinded). Define an abstract method `visit(EnumInterface)` in the enum calling to a new `EnumInterface` with methods corresponding to the enum cases. When you add a constant to the enum, you'll naturally add a method to the `EnumInterface`. – Partly Cloudy Dec 22 '14 at 17:27
  • 1
    @PartlyCloudy - Well yes. But the flipside is that you would force Java to have a more inflexible binary compatibility rule for enum classes. Addition of new values to the enum would *need* to break binary compatibility ... and that would make enum classes a whole lot less useful. – Stephen C Dec 23 '14 at 11:17
  • 4
    If someone adds more constants to the enum, the code which is based on the former version can still fail even if it has "default" branch. Some of old cases may be needed to process in a new fashion now that we have a new constant. So I don't think this is a proper reason. And surely you cannot provide a "default" case more reasonable than just to throw some error, because you cannot be sure to properly process something if you don't know what it is. – Maksim Gumerov Apr 18 '18 at 09:38
  • 3
    You are now talking about the semantics of application rather than the semantics of the programming language. There are *many* cases where it makes sense *from the application perspective* for the `default` case not to be an application error, even in the face of new enum values. – Stephen C Apr 18 '18 at 11:31
14

In Java 12 you can use the preview switch expression feature (JEP-325) as follows:

public static String testSwitch(XYZ xyz) {
    return switch (xyz) {
        case A -> "A";
        case B -> "B";
    };
}

and you don't need default case as long as you handle all enum values in switch.

Note, to use a preview feature you'll have to pass --enable-preview --source 12 options to javac and java

Adrian
  • 2,984
  • 15
  • 27
  • Huh? So what does this return when you modify `XYZ` to add (say) a `C` value, and you **don't** modify / recompile this method? Try it ..... – Stephen C May 14 '19 at 01:21
  • 1
    @StephenC, I'll get compilation error and I knew it that's why I wrote *as long as you treat all enum values* – Adrian May 14 '19 at 06:34
  • You can't get a compilation error if you don't recompile. I'm talking about what happens when modify the enum but don't recompile the method with the switch statement that (now) doesn't cover all of the enum values. – Stephen C May 14 '19 at 06:48
  • For anyone reading this thread: see @StephenC’s addendum to [his own answer](https://stackoverflow.com/a/5013598/27358). – David Moles Feb 27 '20 at 00:24
  • That related to switch statements. Switch expressions are handled a bit differently. The latest iteration of the switch expression JEP seems to says that an implicit default is inserted. What is unclear what the default branch does. I think it throws a runtime exception ... – Stephen C Feb 27 '20 at 00:40
8

As has been stated, you need to return a value and the compiler doesn't assume that the enum cannot change in the future. E.g. you can create another version of the enum and use that without recompiling the method.

Note: there is a third value for xyz which is null.

public static String testSwitch(XYZ xyz) {
    if(xyz == null) return "null";
    switch(xyz){
    case A:
        return "A";
    case B:
        return "B";
    }
    return xyz.getName();
}

This ha the same result as

public static String testSwitch(XYZ xyz) {
     return "" + xyz;
}

The only way to avoid a return is to throw an exception.

public static String testSwitch(XYZ xyz) {
    switch(xyz){
    case A:
        return "A";
    case B:
        return "B";
    }
    throw new AssertionError("Unknown XYZ "+xyz);
}
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • The null value throws an exception already on `switch(xyz)`, no need for a special return here. See [JLS 14.11](http://java.sun.com/docs/books/jls/third_edition/html/statements.html#263129). – Paŭlo Ebermann Feb 16 '11 at 09:18
  • @Paulo, Unless you don't want to throw a `NullPointerException` which it is null. e.g. this does what `String.valueOf(x)` does which does not throw an exception either. – Peter Lawrey Feb 16 '11 at 09:24
1

There is a contract that this method has to return a String unless it throws an Exception. And everytime is not limited to those cases where the value of xyz is equal to XVZ.A or XYZ.B.

Here's another example, where it's obviuos, that the code will run correct but where we have a compiletime error for the very same reason:

public boolean getTrue() {
  if (1 == 1) return true;
}

It is not true that you have to add a default statement, it is true, that you have to return a value at any time. So either add a default statement or add a return statement after the switch block.

Andreas Dolk
  • 113,398
  • 19
  • 180
  • 268
0

what happens when xyz is null in your code example? In that case the method is missing a return statement.

EasterBunnyBugSmasher
  • 1,507
  • 2
  • 15
  • 34
  • 1
    What happens is that you get a `NullPointerException` tracing back to the `switch(xyz)` statement. – Nicolas Favre-Felix Nov 29 '20 at 05:05
  • @NicolasFavre-Felix : no it doesn't. Calling testSwitch(null) doesn't throw a NullPointerException. And it's exactly what the accepted answer is missing: There has to be a return statement for the case of xyz being null. – EasterBunnyBugSmasher Nov 29 '20 at 11:14
  • I'm not sure what you mean, it certainly does throw a `NullPointerException`. [Here is an example](https://gist.github.com/nicolasff/8058ffc3f312e706cc7f98d892200c80) you can try yourself. It does a `switch(null)` and this triggers a `NullPointerException`. You can also refer to [this tutorial about `switch`](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html) on Oracle's website which says explicitly: _Ensure that the expression in any `switch` statement is not null to prevent a `NullPointerException` from being thrown_. – Nicolas Favre-Felix Jan 08 '21 at 22:46
-1

Because compiler cannot guess that there are only two values in the enum and forces you to return value from the method. (However I dont know why it cannot guess, maybe it has something with reflection).

Alex Nikolaenkov
  • 2,505
  • 20
  • 27
  • Yeah, I'm not either why the compiler can't figure out that XYZ has only 2 elements. Since it recognizes XYZ as a type, it has clearly come to the part of the code, and should be able to register the fact that only two possible elements exist. – Ken Wayne VanderLinde Feb 16 '11 at 06:36
  • @rlibby: I recall testing null to see if it made a difference, and I don't think it did. But I will check again. – apoorv020 Feb 16 '11 at 06:37
  • I *think* that you don't need to check for null since there's an implicit call to `ordinal`, but I'm not sure if that's true or not. – templatetypedef Feb 16 '11 at 06:39
  • @rlibby : adding null produced another error (constant expression required). However, actually calling the function with null generates a null pointer exception in the code at switch statement. – apoorv020 Feb 16 '11 at 06:41
  • 1
    @rlibby, `null` in the switch statment will cause an `NPE` so this control path is not valid. – Alex Nikolaenkov Feb 16 '11 at 06:43
-1
default: throw new AssertionError();
irreputable
  • 44,725
  • 9
  • 65
  • 93