Codecs

Last edited 4 minutes ago.

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:

FeatureCodecCompact typeFormat
json (default)JsonCodecVec<u8>JSON bytes via serde_json
msgpackMsgPackCodecVec<u8>MessagePack via rmp-serde
bincodeBincodeCodecVec<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

ConsiderationJSONMessagePackBincode
Human-readable payloads
Payload sizeLargestSmallerSmallest
Serialization speedModerateFastFastest
Schema flexibilityHighHighLow
Debugging easeEasyModerateDifficult
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

CodecFeature flagBest for
JsonCodecjson (default)Debuggability, cross-language compatibility
MsgPackCodecmsgpackReduced payload size, high throughput
BincodeCodecbincodeMaximum performance in Rust-only pipelines
CustomProprietary 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.