A more functional try...catch construct in Java
Thank you for sharing your code and this idea with us! :)

I have just some personal preferences and ideas to some of you "major problems" witch should be additional to the answers of @Mateusz Stefek and @TorbenPutkonen



Not Functional


  public Try&lt;T&gt; catching(Class ex, Function&lt;Exception, T&gt; handler) {
  if(/* ... */) {
    throw new IllegalStateException("exception " + ex.getSimpleName() + " has already been caught");
  } else {
    /* ... */
  }
}



In functional programming attempt not to throw any error instead you would return an Either.


  new Try&lt;&gt;(()-&gt; Integer.parseInt("1"))
                      .catching(NumberFormatException.class, (e) -&gt; 2)
                      .execute();



On each method call on Try you modify it, but fp is all about immutability. 

Some Refactoring

Naming


  public Try&lt;T&gt; catching(Class ex, Function&lt;Exception, T&gt; handler)



I would write out ex to exception. Than we can change the name of


  private final Map&lt;Class, Function&lt;Exception, T&gt;&gt; handlers



to exceptionByHandler, which makes it easier to read, because it suggests a Map more intuitively, than handler which could be a List.

Null check

We have currently two null checks


  // in execute
if(finallyExpr != null)

// in andFinally
if (finallyExpr == null)



The utility class Objects offers the static methods isNull and nonNull and with an static import it could look like

// in execute
if(nonNull(finallyExpr))

// in andFinally
if (isNull(finallyExpr))


But if we use an Optional in andFinally it could look like 

public T andFinally(Runnable newFinallyExpr) {
    finallyExpr = Optional.ofNullable(finallyExpr)
                          .orElseThrow(() -&gt; new IllegalStateException("Cannot have multiple finally expressions"));

    return execute();
}


Non Intuitiv API


  The check in andFinally seems like it's unnecessary. [...]


You already mention this as a flaw.. but from an other perspective. For me as a client it is not intuitive to have the method andFinally and execute to fire up the Try.

More natural would be to let the client call the execute itself and override all finally with the next occurring finally.

new Try&lt;&gt;(()-&gt; Integer.parseInt("f"))
                  .catching(NumberFormatException.class, (e)-&gt; 2)
                  .catching(IllegalStateException.class, (e) -&gt; 3)
                  .andFinally(()-&gt; System.out.println("Finally!"))
                  .andFinally(()-&gt; System.out.println("Finally #2!")) // overrides all previous finally
                  .execute();




Improvement

Make Try immutable

As in functional programming objects are immutable we can do it in Java too. We can let the client modify a Try with methods we provide him and return each time a new Try.

But to build up a Try we can create a TryBuilder witch is mutable. This is a common way which you can find in the Java-World to or example with String and StringBuilder. 

State Pattern


  The need for execute when andFinally isn't used. I can't see how the class would know that all catches have been added though.


This is a scenario for the State Pattern. We have our base-state, which allows the client to add multiple catch-cases. After the last catch-case we need to let the client allow only one finally-case. After this our object is in the execution-state.

Builder Pattern

We can mix the State-Pattern with the Builder-Pattern and on each state we can use a builder, which only let use the methods the client currently needs to make the api more intuitive. 

Replace the Constructor


  The need for new is unfortunate too. I figured I could take a page from Scala and create a static method that acts as a constructor, but then I'd need to do something like Try.tryMethod to reference it, which isn't a whole lot better.


With a static method as constructor we are allowed to return a different reference type.

new Try(/* ... */) // can only return an instance of Try

Try.of(/* ... */)  // can return everything you can imagine 


For the use case I have in mind we need to make the constructor private and return a builder that we need to implement. 

private Try(/* ... */) { /* ... */ }

public  static &lt;T&gt; CatchBuilder of(Supplier&lt;T&gt; tryExpr) {
    return new CatchBuilder&lt;T&gt;(new TryBuilder&lt;T&gt;().withTryExpr(tryExpr));
}


Example Implementation

Try

The Try is a public class with builds the API to the client. It provides the client outside of the package only a of(Supplier&lt;T&gt; tryExpr) to build up a Try. The constructor is package private therewith TryBuilder can access it. With of(Supplier&lt;T&gt; tryExpr) the clients gets a CatchBuilder (this is the modified State-Pattern mentioned above)

public class Try&lt;T&gt; {

  private Supplier&lt;T&gt; tryExpr;

  private Map&lt;Class, Function&lt;Exception, T&gt;&gt; handlers;

  private Runnable finallyExpr;

  Try(Supplier&lt;T&gt; tryExpr,
      Map&lt;Class, Function&lt;Exception, T&gt;&gt; handlers,
      Runnable finallyExpr) {
    this.tryExpr = tryExpr;
    this.handlers = handlers;
    this.finallyExpr = finallyExpr;
  }

  public static &lt;T&gt; CatchBuilder of(Supplier&lt;T&gt; tryExpr) {
    return new CatchBuilder&lt;T&gt;(new TryBuilder&lt;T&gt;().withTryExpr(tryExpr));
  }

  public T execute() {
    try {
      return tryExpr.get();
    } catch (Exception e) {
      if (handlers.containsKey(e.getClass())) {
        Function&lt;Exception, T&gt; handler = handlers.get(e.getClass());
        return handler.apply(e);
      } else {
        throw e;
      }
    } finally {
      if (finallyExpr != null) {
        finallyExpr.run();
      }
    }
  }
}


TryBuilder

This is the mutable way to build up a Try.

class TryBuilder&lt;T&gt; {

  private Supplier&lt;T&gt; tryExpr;

  private Map&lt;Class, Function&lt;Exception, T&gt;&gt; handlers;

  private Runnable finallyExpr;

  public TryBuilder&lt;T&gt; withTryExpr(Supplier&lt;T&gt; tryExpr) {
    this.tryExpr = tryExpr;
    return this;
  }

  public TryBuilder&lt;T&gt; withHandlers(Map&lt;Class, Function&lt;Exception, T&gt;&gt; handlers) {
    this.handlers = handlers;
    return this;
  }

  public TryBuilder&lt;T&gt; withFinallyExpr(Runnable finallyExpr) {
    this.finallyExpr = finallyExpr;
    return this;
  }

  public Try&lt;T&gt; build() {
    return new Try&lt;&gt;(tryExpr, handlers, finallyExpr);
  }

}


CatchBuilder and FinallyBuilder

class CatchBuilder&lt;T&gt; {

  private final Map&lt;Class, Function&lt;Exception, T&gt;&gt; handlers = new HashMap&lt;&gt;();

  private final TryBuilder&lt;T&gt; tryBuilder;

  CatchBuilder(TryBuilder&lt;T&gt; tryBuilder) {
    this.tryBuilder = tryBuilder;
  }

  public CatchBuilder&lt;T&gt; withCatch(Class ex, Function&lt;Exception, T&gt; handler) {
    if (handlers.containsKey(ex)) {
      throw new IllegalStateException("exception " + ex.getSimpleName() + " has already been caught");
    }

    handlers.put(ex, handler);
    return this;
  }

  public FinallyBuilder&lt;T&gt; and(Class ex, Function&lt;Exception, T&gt; handler) {
    withCatch(ex, handler);
    return new FinallyBuilder&lt;&gt;(tryBuilder.withHandlers(handlers));
  }

  public FinallyBuilder&lt;T&gt; onlyCatch(Class ex, Function&lt;Exception, T&gt; handler) {
    return and(ex, handler);
  }

}

class FinallyBuilder&lt;T&gt; {
  private final TryBuilder&lt;T&gt; tryBuilder;

  public FinallyBuilder(TryBuilder&lt;T&gt; tryBuilder) {
    this.tryBuilder = tryBuilder;
  }

  public Try&lt;T&gt; finallyDo(Runnable newFinallyExpr) {
    return tryBuilder.withFinallyExpr(newFinallyExpr)
                     .build();
  }

  public Try&lt;T&gt; withoutFinally() {
    return tryBuilder.build();
  }

}


Exmaple

public static void main(String[] args) {

  Try&lt;Integer&gt; firstTry = Try.of(() -&gt; Integer.parseInt("f"))
                             .withCatch(NumberFormatException.class, (e) -&gt; 2)
                             .and(IllegalStateException.class, (e) -&gt; 3)
                             .finallyDo(() -&gt; System.out.println("Finally!"));

  Try&lt;Integer&gt; secondTry = Try.of(() -&gt; Integer.parseInt("f"))
                              .onlyCatch(NumberFormatException.class, (e) -&gt; 2)
                              .withoutFinally();
}