Implementing a Gen Server and Supervision Tree in Gleam with gleam_otp
Learn how to use gleam_otp to implement Erlang gen_server behaviours and supervision trees in Gleam, with concrete build and verification steps for the Erlang target.
04 Jul 2026, 02:16 UTC

The problem: stateful code needs crash recovery
When you build a long-running service in Gleam, plain functions and immutable data handle most business logic well. The difficulty appears when a process must hold state across many messages and survive failures. If that process crashes, callers should not lose the entire application. Erlang/OTP solves this with behaviours—most notably gen_server—and supervision trees that restart failed workers. Gleam can use these primitives directly through the gleam_otp package, letting you write OTP-compliant processes with Gleam's type system and pattern matching.
Prerequisites and target constraints
This guide assumes you have Gleam and Erlang/OTP installed locally. The critical constraint is the compilation target: OTP behaviours are available only when you compile to Erlang. If your project targets JavaScript, the gleam_otp package will not provide working OTP primitives. Confirm your target before writing code.
Version alignment matters. The gleam_otp API mirrors Erlang's OTP, but it is a separately versioned package. A mismatch between your Gleam compiler and the package can produce compile-time errors. Use the latest compatible releases documented for your Gleam version.
Create the project and add the dependency
Run these commands in your terminal:
gleam new my_otp_app
cd my_otp_app
gleam add gleam_otp
The first command scaffolds a new Gleam project. The third adds gleam_otp to gleam.toml and fetches it. No elevated permissions are required.
Define a gen_server module
A minimal gen_server in Gleam is a module that implements the standard OTP callbacks as plain functions. The core set includes init/0, handle_call/3, handle_cast/2, handle_info/2, and terminate/2. Each returns a Gleam result type that the OTP runtime interprets.
The following example sketches a counter server. Because exact type constructors vary by package version, treat the signatures as illustrative and verify them against the documentation for your installed gleam_otp release.
// src/counter.gleam
import gleam_otp/gen_server
pub type State {
State(count: Int)
}
pub fn init() {
gen_server.InitOk(State(0))
}
pub fn handle_call(state, _request) {
// Return the current count and keep the same state
gen_server.Reply(state, state.count)
}
pub fn handle_cast(state, _message) {
// Accept the cast without changing state
gen_server.NoReply(state)
}
pub fn handle_info(state, _message) {
gen_server.NoReply(state)
}
pub fn terminate(_state, _reason) {
Nil
}
Replace counter.gleam with your own module name. The pattern is consistent: init establishes the starting state, handle_call processes synchronous requests, and the remaining callbacks handle asynchronous messages and shutdown.
Start the server under a supervisor
A gen_server running alone has no automatic restart. To get fault isolation, start it as a child of a supervisor. The gleam_otp package exposes supervisor.start_link, which accepts child specifications referencing your worker module.
In your main module, define the child spec and start the tree:
// src/my_otp_app.gleam
import gleam_otp/supervisor
pub fn main() {
let child_spec = supervisor.worker(counter)
let assert Ok(_sup) =
supervisor.start_link([child_spec], supervisor.OneForOne)
// Keep the Erlang VM alive so the supervisor can restart workers
// (exact sleep/loop mechanism depends on your application structure)
}
The OneForOne strategy means only the crashed child restarts, leaving siblings untouched. Other strategies such as OneForAll or RestForOne are available if your workers share state.
Build, run, and verify
Compile explicitly for the Erlang target:
gleam build -t erlang
A successful build produces Erlang-compatible artifacts under build/dev/erlang/. Run the application with:
gleam run -t erlang
Expected checks:
- The build completes without type errors or missing-module warnings.
- At runtime, the supervisor starts and links to the counter worker.
- A synchronous call to the counter returns the value produced by
handle_call. - If you simulate a crash in the worker, the supervisor restarts it and the state resets according to
init.
To simulate a crash, add a message handler that raises an error and send that message to the process. Observe the supervisor logs to confirm the restart.
Recovery options and limitations
If the build fails with type errors referencing gleam_otp, the most likely cause is a version mismatch. Pin the package to a release documented as compatible with your Gleam compiler, or upgrade both together.
If you accidentally target JavaScript, OTP calls will not resolve. Edit gleam.toml or pass -t erlang explicitly.
Because this guide creates new files rather than modifying an existing deployment, recovery is straightforward: remove the generated modules or use version control to revert the working tree. If you have already started a long-running release, stopping the Erlang VM and rebuilding is the cleanest reset.
Finally, remember that OTP behaviours are an Erlang-target feature. Any code that must also compile to JavaScript should isolate gleam_otp usage behind a target-specific module or keep it out of shared libraries entirely.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.