Skip to main content
Version: 2.x

How to Connect to a Broker

This guide shows how to open a connection to a RabbitMQ broker, how to pick between the three Amqp.connect overloads, and how to read connection settings from the environment, retry a failed connection, and use TLS.

All snippets assume these imports:

import com.rabbitmq.client.{ Connection, ConnectionFactory }
import zio._
import zio.amqp._
import zio.amqp.model._

import java.net.URI

Connect with an AMQPConfigโ€‹

For a broker described by host, port and credentials, AMQPConfig is the most readable option. AMQPConfig.default points at localhost:5672 with the guest user, so a development program only overrides what differs:

val connection: ZIO[Scope, Throwable, Connection] =
Amqp.connect(AMQPConfig.default.copy(host = "rabbit.internal", user = "app", password = "secret"))

The connection is a scoped resource: it is closed when the surrounding scope ends. See Amqp and AMQPConfig for every field.

Connect with a URIโ€‹

When the broker address is a single setting, such as a RABBITMQ_URL environment variable or a secret from a platform, pass it as a URI. The URI carries the credentials, the host, the port and the URL-encoded virtual host (%2F is /):

val connection: ZIO[Scope, Throwable, Connection] =
Amqp.connect(URI.create("amqp://guest:guest@localhost:5672/%2F"))

Use the amqps scheme for TLS.

Connect with a ConnectionFactoryโ€‹

For anything the other two overloads cannot express, such as a custom SSL context, automatic recovery settings, a client-provided name or a custom executor, configure a RabbitMQ ConnectionFactory yourself and pass it in. Amqp.connect(factory) is the overload the other two call:

val connection: ZIO[Scope, Throwable, Connection] = {
val factory = new ConnectionFactory()
factory.setHost("rabbit.internal")
factory.setPort(5672)
factory.setUsername("app")
factory.setPassword("secret")
factory.setRequestedHeartbeat(30)
Amqp.connect(factory)
}

Prefer the factory when a password contains characters that are special in URIs (such as @, : or /): AMQPConfig#toUri does not percent-encode them.

Read the Settings from the Environmentโ€‹

To keep credentials out of the code, build the AMQPConfig from environment variables with System.env, falling back to the defaults:

val config: ZIO[Any, SecurityException, AMQPConfig] =
for {
host <- System.env("AMQP_HOST").map(_.getOrElse(AMQPConfig.default.host))
user <- System.env("AMQP_USER").map(_.getOrElse(AMQPConfig.default.user))
password <- System.env("AMQP_PASSWORD").map(_.getOrElse(AMQPConfig.default.password))
} yield AMQPConfig.default.copy(host = host, user = user, password = password)

val connection: ZIO[Scope, Throwable, Connection] =
config.flatMap(Amqp.connect(_))

If you already use a configuration library, construct the same case class from its output. The library itself has no configuration integration.

Retry Until the Broker Is Availableโ€‹

A container orchestrator often starts the application before the broker accepts connections. Amqp.connect fails with the RabbitMQ client's exception in the error channel, so any ZIO Schedule can retry it. To retry every two seconds, up to ten times:

val connection: ZIO[Scope, Throwable, Connection] =
Amqp.connect(AMQPConfig.default).retry(Schedule.spaced(2.seconds) && Schedule.recurs(10))

Retrying a scoped acquisition is safe: a failed attempt acquired nothing, so nothing is left to release.

Use More than One Channelโ€‹

One connection can serve many channels, and each channel serializes its own operations. To avoid a slow call on one channel delaying another, give independent jobs their own channels on a shared connection:

val program: ZIO[Any, Throwable, Unit] =
ZIO.scoped {
for {
connection <- Amqp.connect(AMQPConfig.default)
publisher <- Amqp.createChannel(connection)
consumer <- Amqp.createChannel(connection)
_ <- publisher.queueDeclare(QueueName("jobs"))
} yield ()
}

The channels are closed first, in reverse order, and then the connection.

Running the Exampleโ€‹

The file ConnectExample.scala opens a connection with each of the three overloads against a local broker and prints whether each is open. Start RabbitMQ with docker compose up -d, then run sbt "zio-amqp-examples/runMain zio.amqp.examples.howto.ConnectExample" from the repository root. The output is:

config open: true, uri open: true, factory open: true

ZIO Chat

Hi, fellow coder! ๐ŸŒŸ Welcome to your ZIO adventure with the ZIO Chat Bot. ๐Ÿš€ Let's master functional Scala together. From referential transparency to concurrent prowess, I've got your back. Ready to dive in? Your questions, my insights โ€“ let's conquer ZIO! ๐Ÿ’ก๐Ÿค–