---
title: "Redis transport"
description: "The babelqueue-redis transport — a canonical-envelope publisher and a URN-routed consumer over the §1 reliable-queue list pattern, built on Lettuce, on the framework-agnostic core."
source: https://babelqueue.com/docs/babelqueue-java/1.x/redis/
updated: 2026-06-14T00:00:00.000Z
---
# Redis transport
`com.babelqueue:babelqueue-redis` is a Redis transport on the Java core, built on the
[Lettuce](https://lettuce.io) client. It sends the
[canonical envelope](/docs/spec/1.x/envelope/) as a Redis list element with the
[§1 reliable-queue pattern](/docs/spec/1.x/broker-bindings/#redis), and consumes by routing each
message to a handler by URN — so a message it produces is consumed by any other BabelQueue SDK,
and vice-versa.
Redis lists carry **no native metadata**, so — unlike the SQS, RabbitMQ or Kafka bindings — there
is **no property projection**. The list element **is** the canonical envelope JSON, byte-for-byte,
with no wrapping and no added fields; routing and tracing read the body directly.
## Install
Maven:
```xml
com.babelqueue
babelqueue-redis
1.0.0
```
Requirements: **Java 17+**. It pulls `babelqueue-core` and `io.lettuce:lettuce-core` transitively.
## Produce
```java
import com.babelqueue.redis.RedisPublisher;
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.sync.RedisCommands;
import java.util.Map;
RedisClient client = RedisClient.create("redis://localhost:6379");
RedisCommands redis = client.connect().sync(); // the command seam
String id = RedisPublisher.create(redis, "orders")
.publish("urn:babel:orders:created", Map.of("order_id", 1042L));
```
`publish(urn, data)` returns the message `meta.id`; an overload adds a `traceId` to continue a
trace. Produce is `RPUSH `.
## Consume
```java
import com.babelqueue.redis.RedisConsumer;
RedisConsumer consumer = RedisConsumer.builder(redis, "orders")
.handler("urn:babel:orders:created", (env, body) -> {
// env.data(), env.traceId(), env.attempts() ...
})
.onError((err, env, body) -> log.warn("bad message", err))
.build();
consumer.run(); // blocking-reserves until the thread is interrupted
```
A reserve atomically moves the head of the queue onto a per-queue `:processing` list
(`BLMOVE :processing LEFT RIGHT`), so an in-flight message survives a crash; a
successful handler `LREM`s it. Retry is **at-least-once**: a throwing handler leaves the element on
the processing list (a recovery sweep can requeue it). The poll loop never stops on a bad message —
observe via `onError` / `onUnknownUrn`. Unknown-URN strategy is one of
`fail` / `delete` / `release` / `dead_letter`.
The command seam is Lettuce's `RedisCommands` interface, so the publisher and
consumer unit-test against a mock with no Redis and no network.
## Contract mapping (§1)
| Envelope | Redis |
| :--- | :--- |
| body | the list element — the canonical envelope JSON, **byte-for-byte, no wrapping** |
| `job` (URN) | read from the body and routed consumer-side (Redis lists carry no native metadata) |
| produce | `RPUSH ` |
| reserve | `BLMOVE :processing LEFT RIGHT` (head → tail; crash-safe in-flight) |
| ack | `LREM :processing 1 ` |
| `attempts` | taken from the body unchanged (Redis has no native delivery counter) |
This is a **Java-owned reliable queue**, mirroring the Go runtime's reliable-queue mechanism —
pointed at a queue this SDK owns end-to-end it is a complete, crash-safe transport. Full parity with
Laravel's reserved-sorted-set reservation on a *shared* PHP+Java Redis queue is a separate task (see
broker-bindings §1.4). The envelope is unchanged (`schema_version` stays `1`); Redis is purely
additive.