---
title: "How to Really Implement Java Exceptions Using an Effect System"
author: Bob Rubbens
publish_timestamp: 2025-11-02T21:33:33+01:00
state: published
template: post.mako
id: f4a4bbf1-47b7-4738-a647-ab21ead21467
---

*This post was a bit rushed, so I apologize for any typos. Please let me know if you spot any.*

[Effects](https://en.wikipedia.org/wiki/Effect_system) are a popular topic in current programming language research that allow combining pure code with side effects more nicely than [monads](https://en.wikipedia.org/wiki/Monad_(functional_programming)) can.

Today I read a post by Varun, titled "[On the purported benefits of effect systems](https://typesanitizer.com/blog/effects-convo.html)". Though I [don't agree with most of the points](https://lobste.rs/s/qxmiqs/on_purported_benefits_effect_systems#c_aorlbc), the post does give a nice overview of what effects can do. Varun writes the following:

> For exceptions, the usefulness comes from the stack trace when the exception is logged. However, you don’t get a stack trace for “free” when you implement support for effects, you need to add support for stack traces separately.

I think this statement is a nice lens to focus on what effect systems can and can't do.

For the rest of this post: I'll give a nutshell introduction to effect systems using an often used analogy to Java exceptions. Then we'll have a look at one possible way we could implement the stack-tracing functionality from exceptions using effects, too.

In this post, I'm throwing both effect type systems and effect handler systems on a pile called "effects" or "effect systems". I feel like I'm missing a distinction here, but I'm not well-versed enough in this direction of programming language theory to know precisely what I'm missing. If you do, and you feel it's important enough to be included, feel free to send me an email and I'll consider including your explanation.

# The "standard" effects example for those who know Java

Effects are often compared to Java exceptions, so we'll also take that comparison as the starting point for our intuition of effects. Consider the following plain Java example that implements a simplistic beehive with one drone that's supposed to get some nectar for the hive:

```java
class NectarNotFound extends Exception { }

class Drone {
  public boolean hasNectar() {
    // No nectar in winter!
    return false;
  }

  public int getNectar() throws ExecutionFailed {
    if (!hasNectar()) {
      throw new NectarNotFound();
    }

    return 1;
  }
}

class Hive {
  public int getNectar() {
    try {
      // Offload nectar responsibilities onto drone
      return new Drone().getNectar();
    } catch (NectarNotFound e) {
      // No nectar, too bad
      return 0
    }
  }
}
```

To make sure we're all on the same page, let's briefly go through the control flow in this program.

1. `getNectar` gets called on a `Hive`.
2. Create `Drone`.
3. Call `getNectar`.
4. Check for nectar with `hasNectar`.
5. In this case, there is no nectar, so `getNectar` throws `NectarNotFound`.
6. The `catch` block surrounding `getNectar` catches the exception, and proceeds to handle the absence of nectar by just returning 0.

We can emulate this control flow using effects in the Koka language. There are other languages we could use, e.g. [Unison](https://www.unison-lang.org) or [Flix](https://flix.dev/). Koka just happens to be the one I have read about before.

```koka
// Declare an effect
effect no_nectar
  ctl no_nectar() : a

fun has_nectar() : bool
  False

// Use "no_nectar" before return type to indicate this function can use the effect
fun drone_get_nectar() : no_nectar int 
  if !has_nectar() 
    // Emit effect instead of returning a value when there's no nectar
    then no_nectar()
    else 1

fun get_nectar() : int
  // Install a handler for the "no_nectar" effect
  // This allows code within the handler to call "no_nectar"
  with handler
    ctl no_nectar() 0
  drone_get_nectar()
```

Barring any spelling mistakes on my part, this Koka program should behave similarly to the earlier Java program: a hive offloads nectar gathering onto a drone, the drone reports that the nectar is not there, and the hive concludes there is 0 nectar. The difference is that in Java, we use built-in exceptions, and in Koka, we use an effect customized for the purpose of reporting that there is no nectar. 

You could argue that, for this example, there is not much difference besides syntax. An important difference, however, is that in Koka effects can be *resumed* (though we don't use that functionality here). In Java terms, this means you can jump back from a `catch` statement to the `throw` statement that threw the original exception, and continue execution right there. While I'm not sure what problems you can solve with that, it sure sounds cool and useful.

If this is all still a bit mysterious to you, no worries. There's another nice (and smaller) example on the [Koka website](https://koka-lang.github.io/koka/doc/book.html#why-handlers) that will probably be more effective at conveying what effects are all about.

# What's wrong with the "standard" example

Now to get back to Varun's statement:

> For exceptions, the usefulness comes from the stack trace when the exception is logged.

The problem with the example in Koka is that it replicates only *half* of the functionality present in the Java example. Yes, the Java and Koka examples both have identical control flow. But in Java you can additionally get a [stack trace](https://en.wikipedia.org/wiki/Stack_trace) from the exception. In the Koka example, there is just no way[^1] to get that automatically. Sure, you could emulate stack tracing manually. But that's clunky and not realistic.[^1]

Summarizing, while comparing effects to exceptions can be useful to understand effects, it doesn't do full justice to the full context of why exceptions are useful.

What would need to change, or be added, to *fully* replicate Java exceptions in Koka, including stack tracing?

# A new effect: function calls

I propose a an effect for instrumenting function calls. All code from this point on is written in a magical Koka dialect that we don't currently have a compiler for, purely for the sake of communicating a cool idea.

```koka
effect call
  ctl call(f L, args... : L.arg_types) : L.return_type
```

The `f` parameter of the effect represents the function being called, with the varargs parameter `args` representing the values of the call. Essentially, the compiler would insert this effect at any call site, allowing you to customize on a case-by-case basis how you want to handle function calls. For example, if you want to do logging of entry and exit of functions, you might write the following handler:

```koka
fun foo() : console int
  // Logging handler to intercept every call
  with handler
    ctl call(f, args) {
      println("entering " + f.name);
      // I'm spitballing here and assuming this will just call the top-level handler for call
      // The top-level handler will "just" call as normal.
      val x = f(args); 
      prinln("leaving " + f.name);
      x
    }
  bar()
```

This would, when executed, print:

```
entering bar ()
... internal stuff of bar omitted ...
leaving bar ()
```

The stack tracing handler would look something like this:

```koka
var stack_trace = StackTrace()
with handler 
  // Install a handler to emulate global state using the above local variable
  state get ... 
with handler
  ctl call(f, args) {
    get().push(f.name)
    val x = f(args);
    get().pop()
    x
  }
// execute rest of program
foo()
```

There are a bunch of problems with this construct:

1. Usability is pretty bad. I guess every function now needs to include *at least* the `call` effect to do anything useful. Maybe you want to include it by default? But then you need syntax to exclude it. That sounds icky...
2. On the flip side, you can now statically tell if a function calls other functions! That can be interpreted as an upside! 😺
3. The compiler will need to emit the `call` effect at every call site. As this is similar to Java using the `String` class from the standard library for string literals, I don't think that's too much of a problem.
4. To get over downside #1, you would at least need some way of stating "default effect sets" concisely, otherwise writing effect types becomes a slog without even having declared any custom effects.
5. Including an effect for something so primitive as function calls probably messes up the "effect hierarchy" that's in the Koka standard library. Sorry, I couldn't find a link for this, but I'm referring to the list of standard effects such as `pure`, `exn`, `div`, `ndet`, etc.
6. How do you even type-check this? Unless you make cool extensions to the type system, this effect can only be a compiler builtin. Or you generate all necessary variations of the `call` effect beforehand... That doesn't sound practical at all. I think you can actually make a pretty reasonable [Prolog](https://en.wikipedia.org/wiki/Prolog)-like extension to the type system that, while specific, would at least ensure you don't need to touch compiler internals to make this effect work.
7. Not having stack traces *by default* sounds painful. Then again, making it possible for users to decide if they want to pay the runtime overhead of stack traces sounds like a nice feature. 😺
8. We're hiding mutation of a local variable in an effect handler. Is that safe?

# Do you seriously think this is a good idea?

Maybe! Would you like to fund my research?

Clearly, there are problems that need to be solved. But I'm pretty confident that, a priori, effects could be used as a tool to also implement the stack tracing part of exceptions. I even think they could be a suitable technique to consider.

But, more importantly, I want to illustrate that, there are many ways to solve problems using effects. As with any programming construct, in cases where you think effects don't work, if you look at it the right way and squint at it, you can probably think of a way to *make* it work. That doesn't mean you *should* do it. But it's worth considering, and definitly shouldn't be disregarded offhand.

Case in point: I once explained to H how to use `if` statements in matlab. She responded to me, "oh, I always use `for` loops to do that." I aspire to be *that* smart one day.

[^1]: Afaik. Email me otherwise.