--- 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.