Codecs
On this page
Every task that flows through an Apalis backend is serialized before storage and deserialized before your handler receives it. This conversion is handled by a codec — a type that implements the Codec trait and is associated with a backend via BackendExt.
apalis-codec provides ready-made codecs for the most common formats, and the Codec trait is straightforward to implement for custom needs.
The Codec Trait
pub trait Codec<T> {
/// The encoded (stored) representation of `T`.
type Compact;
/// The error type returned if encoding or decoding fails.
type Error;
/// Encode a value into its compact representation.
fn encode(input: &T) -> Result<Self::Compact, Self::Error>;
/// Decode a compact representation back into `T`.
fn decode(compact: &Self::Compact) -> Result<T, Self::Error>;
}The Compact associated type is the backend's storage format — for example Vec<u8> for binary formats or String for text-based ones. T is your job argument type. The codec sits between the two, encoding on the way in and decoding on the way out.
You generally never call Codec methods directly — TaskSink calls encode when pushing, and Backend::poll calls decode before handing a task to the worker.
Available Codecs
apalis-codec ships three codecs behind feature flags:
| Feature | Codec | Compact type | Format |
|---|---|---|---|
json (default) | JsonCodec | Vec<u8> | JSON bytes via serde_json |
msgpack | MsgPackCodec | Vec<u8> | MessagePack via rmp-serde |
bincode | BincodeCodec | Vec<u8> | Bincode via the bincode crate |
All three require your job type to implement serde::Serialize and serde::Deserialize.
JSON (default)
apalis-codec = "0.1"JSON is the default codec used by most Apalis backends. It produces human-readable payloads, which makes debugging straightforward — you can inspect stored tasks directly in your database.
use apalis_codec::json::JsonCodec;
let encoded: Vec<u8> = JsonCodec::encode(&my_job)?;
let decoded: MyJob = JsonCodec::decode(&encoded)?;MessagePack
apalis-codec = { version = "0.1", features = ["msgpack"] }MessagePack produces compact binary payloads — typically 20–50% smaller than equivalent JSON. Use it when storage size or serialization throughput matters and human-readable payloads are not required.
use apalis_codec::msgpack::MsgPackCodec;
let encoded: Vec<u8> = MsgPackCodec::encode(&my_job)?;
let decoded: MyJob = MsgPackCodec::decode(&encoded)?;Bincode
apalis-codec = { version = "0.1", features = ["bincode"] }Bincode is a compact, non-self-describing binary format optimised for speed. It produces the smallest payloads of the three options but is the least portable — decoded data must be read with the same type definition it was encoded with.
use apalis_codec::bincode::BincodeCodec;
let encoded: Vec<u8> = BincodeCodec::encode(&my_job)?;
let decoded: MyJob = BincodeCodec::decode(&encoded)?;Choosing a Codec
| Consideration | JSON | MessagePack | Bincode |
|---|---|---|---|
| Human-readable payloads | ✓ | ✗ | ✗ |
| Payload size | Largest | Smaller | Smallest |
| Serialization speed | Moderate | Fast | Fastest |
| Schema flexibility | High | High | Low |
| Debugging ease | Easy | Moderate | Difficult |
| Cross-language compatibility | ✓ | ✓ | ✗ |
JSON is the right default for most applications — readable, flexible, and widely supported. Switch to MessagePack when you have high task volume and want to reduce storage overhead without sacrificing schema flexibility. Use Bincode only in performance-critical, Rust-only pipelines where you control both the producer and consumer.
How Codecs Integrate with Backends
The codec is declared as part of the backend's BackendExt implementation via the Codec associated type:
pub trait BackendExt: Backend {
type Codec: Codec<Self::Args, Compact = Self::Compact>;
type Compact;
// ...
}This means the codec is fixed per backend type — all tasks pushed to a given backend use the same encoding. When you call TaskSink::push, the codec's encode is called automatically. When the worker polls and receives a task, decode is called before the handler sees the arguments.
If encoding fails, a TaskSinkError::CodecError is returned. If decoding fails, the task is surfaced as an error on the worker's stream.
Implementing a Custom Codec
If the built-in codecs do not suit your needs — for example, you need to store tasks as serde_json::Value for schema-flexible querying, or you have a proprietary binary format — implement Codec directly:
use apalis_core::backend::codec::Codec;
pub struct MyCodec;
impl<T> Codec<T> for MyCodec
where
T: serde::Serialize + for<'de> serde::Deserialize<'de>,
{
type Compact = String;
type Error = serde_json::Error;
fn encode(input: &T) -> Result<String, Self::Error> {
serde_json::to_string(input)
}
fn decode(compact: &String) -> Result<T, Self::Error> {
serde_json::from_str(compact)
}
}Here Compact = String rather than Vec<u8> — useful for backends like PostgreSQL where storing tasks as a TEXT or JSONB column makes them directly queryable with SQL.
Summary
| Codec | Feature flag | Best for |
|---|---|---|
JsonCodec | json (default) | Debuggability, cross-language compatibility |
MsgPackCodec | msgpack | Reduced payload size, high throughput |
BincodeCodec | bincode | Maximum performance in Rust-only pipelines |
| Custom | — | Proprietary formats, queryable storage types |
Codecs are the only layer in Apalis that touches the raw bytes of your task payload. Keeping them swappable via a trait — rather than hardcoding a format — means you can change serialization strategy per backend without touching your handler logic.