Class NonFatal

java.lang.Object
dmx.fun.NonFatal

@NullMarked public final class NonFatal extends Object
The library's fatal-throwable policy: classifies a Throwable as non-fatal — safe to capture, map, and recover from as an ordinary failure value — or fatal, signaling a condition that should propagate rather than travel through a pipeline.

The fatal set is:

Everything else — including other Error subclasses such as AssertionError — is considered non-fatal. This follows the spirit of Scala's scala.util.control.NonFatal, whose fatal set additionally includes ThreadDeath and ControlThrowable — the former is deprecated for removal and no longer thrown by the JVM, and the latter has no Java analogue.

CancellationException is deliberately non-fatal, even though it also signals cancellation: it is the library's own convention to surface a cancelled future as an ordinary failure value (Try.fromFuture manufactures exactly that), and unlike InterruptedException it carries no thread-local interrupt flag that swallowing would lose.

check(Throwable) classifies only the given throwable. Borders that need chain-aware handling (interruption in particular often arrives wrapped as another exception's cause) should use rethrowIfFatal(Throwable) or Try.rethrowFatal().

See Also:
  • Method Details

    • check

      public static boolean check(Throwable throwable)
      Returns true if throwable is non-fatal.
      Parameters:
      throwable - the throwable to classify; must not be null
      Returns:
      true if non-fatal, false if fatal
      Throws:
      NullPointerException - if throwable is null
    • rethrowIfFatal

      public static void rethrowIfFatal(Throwable throwable)
      Rethrows throwable if it — or any throwable reachable through its cause chain and suppressed exceptions — is fatal per check(Throwable); otherwise returns normally.

      If an InterruptedException is reachable, the current thread's interrupt flag is set before anything is thrown — note this propagates the interruption to the calling thread, which is not necessarily the thread the exception was raised on. A fatal Error takes priority and is rethrown as-is. Otherwise an interruption is rethrown unchecked: as throwable itself when it already is a CompletionException (not wrapped again), and as new CompletionException(throwable) otherwise — the full original graph stays reachable through the cause.

      CompletionException is also the transport wrapper the library's future borders deliberately strip (Try.fromFuture and friends), so a rethrown interruption that crosses a future boundary comes back as an ordinary Failure — with the InterruptedException intact as its cause. The rethrow therefore guards a synchronous border only: after a future round-trip, apply Try.rethrowFatal() again on the receiving side and the interruption is re-detected.

      Traversal is bounded at 1000 throwables — a backstop against hostile getCause() overrides — so a fatal parked beyond that bound goes undetected. Cause cycles are detected without allocation (Floyd's algorithm) and abandoned, so a cycle neither loops nor starves the rest of the graph. The dominant shapes — a plain cause chain, or one level of suppressed exceptions as parked by Resource — are traversed without building auxiliary structures (though getSuppressed() itself clones its array when non-empty); a worklist is built only when a suppressed throwable carries a graph of its own.

      Parameters:
      throwable - the throwable to inspect; must not be null
      Throws:
      NullPointerException - if throwable is null
      Error - if a fatal Error is reachable within the traversal bound
      CompletionException - if an InterruptedException is reachable and no fatal Error is present