41

Generally speaking, the Java compiler does not propagate the information that a method "always" throw an Exception, and therefore, does not detect that all code paths are complete.

(This is due to the fact that Java compiler compiles each class independently).

It's a problem when you want to write something like that.

public class ErrorContext {
    public void fatalISE(String message) {
        String context = "gather lots of information about the context of the error";
        throw new IllegalStateException(context +": " + message);
    }
}

public class A {
    public MyObject myMethod() {
        if (allIsGood()) {
            return new MyObject();
        }
        ErrorContext.fatalISE("all is not good");
    }
}

(ie, a kind of "assertion helper" that gathers context information).

Because the compiler will complain that myMethod does not always return a MyObject.

To my knowledge, there is no specific annotation to indicate that a method always throws.

Zorglub
  • 2,077
  • 1
  • 19
  • 22

6 Answers6

50

A simple workaround is to let your fatalISE method not throw the exception, but only create it:

public class ErrorContext {
    public IllegalStateException fatalISE(String message) {
        String context = "gather lots of information about the context of the error";
        return new IllegalStateException(context +": " + message);
    }
}

public class A {
    public MyObject myMethod() {
        if (allIsGood()) {
            return new MyObject();
        }
        throw ErrorContext.fatalISE("all is not good");
    }
}

This way the compiler will know not to complain about a missing return. And forgetting to use the throw is unlikely, exactly because the compiler will usually complain.

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
  • 11
    This has the *big* advantage of clarity when reading the code. – T.J. Crowder Feb 22 '13 at 08:19
  • This solution looks to me as the cleanest way to solve the _"Missing return statement"_ in several situations including methods with **switch** clauses where exceptions are thrown in any of the branches. It just adds the overhead of writing the _throw_ keyword once per method usage. – Alvaro Lazaro Jul 21 '20 at 21:26
10

A trick that I use is to replace

public void fatalISE(String message) {
    String context = "gather lots of information about the context of the error";
    throw new IllegalStateException(context +": " + message);
}

with

public <T> T fatalISE(String message) {
    String context = "gather lots of information about the context of the error";
    throw new IllegalStateException(context +": " + message);
}

Then, in myMethod, use:

public MyObject myMethod() {
   if (allIsGood()) {
        return new MyObject();
    }
    return ErrorContext.fatalISE("all is not good");
}

It will work whatever the return type of myMethod, including primitive types. You can still use fatalISE in a void method, by just not using the returnkeyword.

Zorglub
  • 2,077
  • 1
  • 19
  • 22
  • 5
    This is clever (quite clever), but a bit misleading to people reading the code. – T.J. Crowder Feb 22 '13 at 08:22
  • 2
    Seems to work for functions that return references, but not primitives: `type parameters of T cannot be determined; no unique maximal instance exists for type variable T with upper bounds int,java.lang.Object` on the line `return this.fatalISE("blah");` – T.J. Crowder Feb 22 '13 at 08:23
8

How about reversing the if condition?

public MyObject myMethod() {
    if (!allIsGood()) {
        ErrorContext.fatalISE("all is not good");
    }
    return new MyObject();
}

Good luck!

vikingsteve
  • 38,481
  • 23
  • 112
  • 156
6

add

return null;

at the end. (it will never reach there anyway, but to silent the compiler this should do)

Mordechai
  • 15,437
  • 2
  • 41
  • 82
3

What you're trying to do is declare that a function never returns, which isn't something that Java knows how to do. Kotlin, for contrast, has a Nothing type. If you declare that your function returns "Nothing", that means it never returns.

Zorglub's answer above, with the type parameter that a function returns the type parameter "T" is the closest you can come to this in Java. Here's something I wrote recently, which logs a string and then throws an exception with that same string:

/**
 * Error logging. Logs the message and then throws a {@link RuntimeException} with the given
 * string.
 *
 * @param tag String indicating which code is responsible for the log message
 * @param msg String or object to be logged
 */
@CanIgnoreReturnValue
@SuppressWarnings("TypeParameterUnusedInFormals")
public static <T> T logAndThrow(String tag, Object msg) {
    String s = msg.toString();
    Log.e(tag, s);
    throw new RuntimeException(s);
}

This code, using Android's standard logging API, is something I want to be able to use anywhere that a value might be called for. I want to be able to have a lambda whose body is just a call to this function. This solution turns out to work great, but it's worth explaining some of the tricky bits:

  • You can't do this somewhere that a Java primitive type (int, double, boolean, etc.) is expected.
  • The two @-annotations are necessary if you're using error-prone (a fantastic static Java bug checking tool), because it will otherwise complain at the callsite that you're not using the return value and it will complain at the method definition that you're doing unspeakably evil things with the type parameter.
  • Thanks for the statement _What you're trying to do is declare that a function never returns, which isn't something that Java knows how to do_. It explains precisely _why_ the compiler cannot handle such situations. – riccardo.cardin Dec 10 '19 at 10:25
1

I just came across this use case but with a method that should always throw 2 or more type of exceptions.

To make the code compile you can add return null; As MouseEvent said.

or better replace it with throw new AssertionError() that prevent returning null value, improve readability and ensure that if someone modify checkAndAlwaysThrowException() it wil continue to Always throw exception

public Object myMehtod() throws ExceptionType1, ExceptionType2 {
    //....
    checkAndAlwaysThrowException();
    throw new AssertionError("checkAndAlwaysThrowException should have throw exception");
}

public void checkAndAlwaysThrowException() throws ExceptionType1, ExceptionType2 {
    if (cond) {
        throw new ExceptionType1();
    } else {
        throw new ExceptionType2();
    }
}
user43968
  • 2,049
  • 20
  • 37