---
title: "Crossfire: Custom Matchmaking Algorithms"
description: "[Namazu Crossfire](../namazu-crossfire) ships two built-in matchmaking algorithms — a FIFO queue and a simple join-code flow — but both matching strategies are pluggable. This page walks through how the built-in algorithms actually work end-to-end, and what you get for free…"
url: "https://namazustudios.com/docs/add-ons/crossplay-gaming-backend/crossfire-custom-matchmaking-algorithms/"
updated: "2026-08-14"
category: "Add-Ons / Crossplay"
---

# Crossfire: Custom Matchmaking Algorithms

[Namazu Crossfire](../namazu-crossfire) ships two built-in matchmaking algorithms — a FIFO queue and a simple join-code flow — but both matching strategies are pluggable. This page walks through how the built-in algorithms actually work end-to-end, and what you get for free when you write your own (ratings-based matching, party/lobby systems, region-aware pools, and so on).

This assumes familiarity with the [Crossfire Protocol Reference](../crossfire-protocol-reference), in particular the four handshake flows (`FIND`/`JOIN` vs. `CREATE`/`JOIN_CODE`).

---

## Two algorithm interfaces

There are two [Distinct](https://namazustudios.com/glossary/distinct/) extension points, corresponding to the two handshake flow pairs — they are not variants of each other, and a given implementation only serves one:

- **`FindMatchmakingAlgorithm`** — serves `FIND` (`initialize`) and `JOIN` (`resume`). The built-in implementation is `FIFOMatchmakingAlgorithm`.
- **`JoinCodeMatchmakingAlgorithm`** — serves `CREATE` (`initialize`) and `JOIN_CODE` (`resume`). The built-in implementation is `SimpleJoinCodeMatchmakingAlgorithm`.

Both extend the common `MatchmakingAlgorithm<CreateT, ResumeT>` contract:

- `initialize(request)` — called for the "first contact" request (`FIND` or `CREATE`). Must be **non-blocking** and return a `MatchHandle` immediately.
- `resume(request)` — called for a "second contact" request (`JOIN` or `JOIN_CODE`). Also non-blocking, also returns a `MatchHandle`.

## The `MatchPhase` lifecycle

Every `MatchHandle` tracks its own phase, independent of the connection's `ConnectionPhase`:

```
READY → MATCHING → MATCHED → TERMINATED
```

![MatchPhase state machine: READY to MATCHING to MATCHED to TERMINATED, with cancellation transitions to TERMINATED from READY and MATCHING](images/matchphase-lifecycle.svg)

`TERMINATED` is absorbing — every transition first checks for it and no-ops if already there, so a handle can never come back to life once the player has disconnected or left. Transitions are otherwise strict and enforced by the state record itself: `startMatching()` requires `READY`, and reporting a result requires `MATCHING`; calling either out of order throws `ProtocolStateException` rather than silently doing nothing.

## What `AbstractMatchHandle` gives you

`AbstractMatchHandle<RequestT>` (in the `util` module) is the base class every concrete `MatchHandle` should extend. It owns the `MatchPhase` state machine in an `AtomicReference` and turns each public `MatchHandle` method into a phase-checked dispatch to one of six abstract hooks you implement:

- `onMatching(state)` — do the actual work of finding or creating a match. This is the only hook every algorithm must supply itself; there's no generic implementation of "how do I match."
- `onResult(state, result)` — called once you invoke `setResult(...)` from within `onMatching`, completing the handshake.
- `onLeaveMatch(state)` — the player is leaving a match they were already matched into.
- `onOpenMatch(state)` / `onCloseMatch(state)` / `onEndMatch(state)` — back the `OPEN`/`CLOSE`/`END` control messages. These don't change `MatchPhase` themselves — a match can be opened and closed repeatedly while `MATCHED`.

Two of these dispatches are worth studying closely, because they demonstrate the `updateAndGet` vs. `getAndUpdate` distinction called out in the [protocol reference](../crossfire-protocol-reference#h-implementation-notes):

- `startMatching()` uses **`updateAndGet`** — it needs the *resulting* phase to decide whether to actually kick off `onMatching`, or just log if the handle was already terminated (e.g. the player disconnected before matching started).
- `leaveMatch()` uses **`getAndUpdate`** — it needs the phase *before* the termination transition overwrote it, because the cleanup differs: a `MATCHED` handle has a real match/result to release (`onLeaveMatch`), while a still-`MATCHING` handle has nothing to release yet (falls through to a log-only default).

Inside `onMatching`, call the protected `setResult(MultiMatch)` once you've found or created the match — this is what drives the `MATCHING → MATCHED` transition and ultimately calls `getRequest().success(this)`, which is what actually sends the handshake response and moves the connection into `SIGNALING`.

## Skipping the boilerplate with `StandardCancelableMatchHandle`

`StandardCancelableMatchHandle<RequestT>` (also in `util`) implements every hook except `onMatching` in terms of the standard `MultiMatchDao` operations, so most custom algorithms only need to extend it and write `onMatching`:

- `onEndMatch` → `DAO.endMatch(matchId)`
- `onCloseMatch` → `DAO.closeMatch(matchId)`
- `onOpenMatch` → re-fetches the match, then `DAO.openMatch(...)`
- `onLeaveMatch` → `DAO.removeProfile(matchId, Profile)`, and deletes the match if that was the last [Profile](https://namazustudios.com/glossary/profile/)
- `onResult` → `getRequest().success(this)`

Every [DAO](https://namazustudios.com/glossary/dao/) call in these hooks (other than `onOpenMatch`) is dispatched via `getRequest().getServer().submit(...)` so it runs off the calling thread — your `onMatching` override should follow the same pattern rather than blocking on database access directly.

## Walkthrough: `FIFOMatchmakingAlgorithm`

`FIFOMatchmakingAlgorithm` is the reference implementation to model a new `FindMatchmakingAlgorithm` on:

1. `initialize(request)` returns a private inner `FIFOMatchHandle`, extending `StandardCancelableMatchHandle<FindHandshakeRequest>`.
2. `resume(request)` returns a plain `StandardJoinMatchHandle` — a generic, protocol-level handle (not FIFO-specific) that re-looks-up the existing match by [id](https://namazustudios.com/glossary/id/) and verifies the requesting [Profile](https://namazustudios.com/glossary/profile/) is a member. Reconnection logic doesn't depend on how the match was originally formed, so any `FindMatchmakingAlgorithm` can reuse this class as-is for its `resume()`.
3. `FIFOMatchHandle.onMatching` submits work to the server executor: opens a transaction, calls `MultiMatchDao.findOldestAvailableMultiMatchCandidate(configuration, profileId, "")`; if nothing is available it creates a brand-new `OPEN` `MultiMatch`; either way it adds the requesting [Profile](https://namazustudios.com/glossary/profile/) and calls `setResult(...)`.

A ratings-based or region-aware algorithm would follow the identical shape — swap step 3's [DAO](https://namazustudios.com/glossary/dao/) query for your own candidate-selection logic (e.g. querying a rating [Service](https://namazustudios.com/glossary/service/) and filtering candidates by rating window before falling back to creating a new match).

## Walkthrough: `SimpleJoinCodeMatchmakingAlgorithm`

The create/join-code flow differs from FIFO in a few instructive ways:

- It exposes two deployment-configurable [Element](https://namazustudios.com/glossary/element/) attributes — `JOIN_CODE_LENGTH` (default `4`) and `MAX_ATTEMPTS` (default `2000`) — rather than hardcoding join-code generation parameters.
- `initialize()`'s handle creates the match via a join-code-aware [DAO](https://namazustudios.com/glossary/dao/) overload, `DAO.createMultiMatch(match, parameters)`, where `parameters` (a `UniqueCodeDao.GenerationParameters`) carries the code length/attempt budget along with the app config's timeout/linger seconds.
- It **overrides `newHandshakeResponse()`** on its handle to return a `CreatedHandshakeResponse` (populated with `matchId`, `joinCode`, `profileId`) instead of the default `MatchedResponse`. This is the pattern to copy any time your algorithm needs to hand extra data back to the client in the handshake response — `MatchHandle`'s default `newHandshakeResponse()` only returns a bare `MatchedResponse`.
- `resume()` (serving `JOIN_CODE`) uses `StandardJoinCodeMatchHandle`, whose `onMatching` looks the match up by join code (`DAO.getMultiMatchByJoinCode(...)`) and adds the joining [Profile](https://namazustudios.com/glossary/profile/) if it isn't already a member — unlike FIFO, where [Profile](https://namazustudios.com/glossary/profile/)-adding only happens in `initialize()`, here it can also happen during `resume()` since that's when a second player actually shows up.

## Registering your algorithm

Export your implementation with `@ElementServiceExport` and bind it in your [Element](https://namazustudios.com/glossary/element/)'s [Guice](https://namazustudios.com/glossary/guice/) module. To make an algorithm selectable by [name](https://namazustudios.com/glossary/name/) from a `MatchmakingApplicationConfiguration.matchmaker` reference (rather than only usable as your [Element](https://namazustudios.com/glossary/element/)'s default), bind it twice — once unqualified and once under a `@Named` annotation — following the exact pattern Crossfire itself uses for FIFO and simple join-code:

```
@ElementServiceExport(value = FindMatchmakingAlgorithm.class)
@ElementServiceExport(value = FindMatchmakingAlgorithm.class, name = "RATING_WINDOW")
public class RatingWindowMatchmakingAlgorithm implements FindMatchmakingAlgorithm { /* ... */ }
```

```
bind(FindMatchmakingAlgorithm.class).to(RatingWindowMatchmakingAlgorithm.class);
bind(FindMatchmakingAlgorithm.class)
    .annotatedWith(named("RATING_WINDOW"))
    .to(RatingWindowMatchmakingAlgorithm.class);
expose(FindMatchmakingAlgorithm.class);
expose(FindMatchmakingAlgorithm.class).annotatedWith(named("RATING_WINDOW"));
```

The unqualified binding becomes the algorithm used whenever a `MatchmakingApplicationConfiguration` doesn't specify a `matchmaker`; the named binding is what `V1HandshakeHandler.algorithmFromConfiguration()` resolves when a configuration explicitly references your algorithm by its `ElementServiceReference` [name](https://namazustudios.com/glossary/name/) — see [Custom Elements](../custom-elements) for background on cross-[Element](https://namazustudios.com/glossary/element/) [Service](https://namazustudios.com/glossary/service/) resolution.
