Skip to content
gen_server
step 1/5

Reading — step 1 of 5

Learn

~1 min readOTP Behaviours

gen_server is OTP's standard behaviour for stateful server processes. Implements the receive-loop pattern with hooks for sync call, async cast, and lifecycle.

-module(counter).
-behaviour(gen_server).
-export([start_link/0, increment/0, value/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

%% Client API
start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, 0, []).
increment() -> gen_server:cast(?MODULE, increment).
value() -> gen_server:call(?MODULE, get).

%% Server callbacks
init(Initial) -> {ok, Initial}.

handle_call(get, _From, State) -> {reply, State, State}.
handle_cast(increment, State) -> {noreply, State + 1}.
handle_info(_Msg, State) -> {noreply, State}.
terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.

Two-part split:

  • Client API — what callers use (start_link, increment, value)
  • Server callbacks — internal protocol (init, handle_call, ...)

This is THE Erlang convention. Hides the gen_server protocol behind a clean API.

Return shapes for callbacks:

%% handle_call:
{reply, Reply, NewState}
{reply, Reply, NewState, Timeout}
{reply, Reply, NewState, hibernate}
{noreply, NewState}              %% no reply yet — manual reply later
{stop, Reason, Reply, NewState}

%% handle_cast:
{noreply, NewState}
{stop, Reason, NewState}

call vs cast:

  • call — synchronous, caller waits for reply. Use for queries.
  • cast — asynchronous, returns ok immediately. Use for fire-and-forget actions.

Why gen_server over hand-rolled receive loops:

  • Tested protocol — handles caller crashes, monitors, timeouts
  • Standard message format — tools (observer, recon) can introspect
  • Plays nicely with supervisors
  • Hot code reload via code_change

For Judge0 — the build won't compile a -behaviour module unless gen_server is present (it is — part of OTP). The escript-style template works:

-module(main).
-export([main/1]).
%% gen_server can be defined inline as a separate module... but for one-shot scripts,
%% manual receive loops are usually simpler.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…