# Apache BifroMQ: a distributed MQTT broker built for multi-tenant device fleets

> BifroMQ is an Apache incubating project that implements MQTT 3.1, 3.1.1 and 5.0 over TCP, TLS, WebSocket and secure WebSocket, with multi-tenancy and resource isolation built into the broker rather than bolted on. It also ships its own distributed storage engine, which is the decision that makes it different and the decision that makes it hard to evaluate.

**apache/bifromq** — Apache BifroMQ (Incubating) is a high-performance, distributed MQTT broker that natively supports multi-tenancy. It is designed to enable the building of large-scale IoT device connectivity and messaging systems.

- Repository: https://github.com/apache/bifromq
- Website: https://bifromq.apache.org/
- Stars: 805 · Forks: 99
- Language: Java
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/apache-bifromq

## The differentiator is a storage engine with no third-party middleware

Apache BifroMQ is a distributed MQTT broker that natively supports multi-tenancy, designed to enable large-scale IoT device connectivity and messaging systems. It is in incubation, and it is worth being clear about what that means: the repository and the release naming both carry the incubating marker, and the Apache Software Foundation disclaimer applies.

The feature that separates it from a general-purpose broker is the third bullet in the list. BifroMQ includes a built-in distributed storage engine optimized for MQTT workloads, with no third-party middleware dependencies.

That is a large claim and a large decision. Most MQTT brokers either embed a single-node database and leave clustering to the operator, or require you to run a separate storage system such as a replicated database or a key-value cluster, which becomes part of your architecture and part of your failure domain. BifroMQ makes its own storage layer part of the broker, which means the cluster topology, the replication behaviour and the data model are the project's choices rather than yours.

The trade-off is the mirror image. You gain fewer moving parts and a storage engine shaped for the access pattern of MQTT, and you take on the responsibility of evaluating a storage engine that is young, part of an incubating project, and not separable from the broker. Whether that is a good deal depends on whether your alternative was running a database cluster anyway.

The rest of the broker is conventional and complete. Full support for MQTT 3.1, 3.1.1 and 5.0 features over TCP, TLS, WS and WSS covers the protocol versions and the transports a real deployment needs.

## Multi-tenancy is native, and isolation is per tenant rather than per cluster

Native support for multi-tenancy resource sharing and workload isolation is the second headline feature, and the word native is doing the work there. The broker understands tenants as a first-class concept rather than as a naming convention applied to topics.

The extension mechanism shows what a tenant is actually made of. The broker supports extension points for authentication and authorization, for tenant-level runtime settings, for tenant-level resource throttling, for events, and for system and tenant-level metrics.

Read that list together and you can see the operational shape. A tenant has its own configuration, its own rate limits, its own metrics, and its own authentication and authorization decisions, all while sharing the same cluster with the other tenants. Throttling in particular is the capability that makes shared infrastructure defensible: one tenant cannot exhaust the broker for everyone else, because the limit is a per-tenant setting rather than a global one you tune by hand when you notice trouble.

The practical consequence is that a single BifroMQ deployment can front many separate logical products, each with its own user base, without running a broker per customer. The repository layout reinforces this. There are separate modules for the API server, the MQTT protocol layer, the deliverer, the inbox, the retain service, the session dictionary, the plugin system and the metrics, plus a set of base modules named for the primitives the rest is built on, including base-cluster, base-crdt, base-env, base-hlc, base-hookloader, base-kv, base-logger, base-rpc, base-scheduler and base-util.

The base-crdt and base-hlc modules are the visible cost of a distributed storage engine. A CRDT data structure and a hybrid logical clock are exactly the tools you need for convergent state across nodes, and they are rarely things you would want to write for a message broker.

## Running one node: the memory limit is the setting you must not skip

The Docker quick start is one command, and it has a trap in it:

```bash
docker run -d -m <MEM_LIMIT> -e MEM_LIMIT='<MEM_LIMIT_IN_BYTES>' --name bifromq -p 1883:1883 apache/bifromq:${TAG}
```

The memory limit has to be supplied twice, once as the Docker memory flag and once as the MEM_LIMIT environment variable, and the documentation explains why with a concrete failure mode. If it is not specified, BifroMQ defaults to using the hosting server's physical memory for determining its JVM parameters, which can result in the Docker process being terminated by the host's Out-of-Memory killer.

So the default behaviour in a container is a JVM sized for the whole machine, running inside a container that the host may kill without warning. The example in the docs uses 2G for the memory limit and 2147483648 for the byte value. The port mapping is the standard MQTT one, 1883.

This is a small detail that shows the project's care about production deployment, and it also shows that the memory sizing is the first thing to understand about the broker, since the JVM heap is derived from it.

The image itself is built on Debian with a multi-stage Dockerfile. The first stage is a verifier that copies the release tarball and its sha512 checksum, checks the checksum, and unpacks the archive. The runtime stage installs a headless OpenJDK 17 JRE along with a set of diagnostic tools, net-tools, lsof, netcat-openbsd, procps and less, and it creates a dedicated unprivileged user and group with uid and gid 1000 before switching to it. The verifier stage means the published image is built from a checksummed release artefact rather than from a working tree.

## A three node cluster on one host, and the seedEndpoints that make it a cluster

The documentation walks through building a cluster with Docker Compose on a single host for development and testing, with three nodes named node1, node2 and node3. Each node has its own configuration file, and that file is where cluster membership is decided:

```yml
clusterConfig:
  env: "Test"
  host: bifromq-node1 # Change this to bifromq-node2 for node2 and bifromq-node3 for node3
  port: 8899
  seedEndpoints: "bifromq-node1:8899,bifromq-node2:8899,bifromq-node3:8899"
```

The host field identifies the node and must be changed per node, and the port is the cluster port, 8899, which is separate from the MQTT port the clients use. The seedEndpoints line lists all three nodes, and it is the same on every node, which is what lets a node discover the others when it starts with no prior membership.

The compose file mounts each node's standalone.yml into /home/bifromq/conf/standalone.yml and publishes a different host port for each node, 1883, 1884 and 1885, all mapping to the container port 1883. The three containers share a bridge network named bifromq-net, which is what makes the hostnames in seedEndpoints resolvable.

The image tag is a variable, ${TAG}, and the memory limit is set per service in the environment block. Then one command brings it up:

```shell
docker compose up -d
```

One thing this setup does not cover is data durability across restarts, because there is no volume mapping in the compose file. That is a reasonable choice for a development and testing cluster, and it is worth noticing if you copy the pattern into production.

## Building from source needs JDK 17, Maven, and a decision about TLS

The build prerequisites are JDK 17 or newer, Maven 3.5.0 or newer, and optionally OpenSSL on the host, which the documentation notes is needed for native TLS, with TLS falling back to the JDK implementation if OpenSSL is absent.

The build itself uses the Maven wrapper, so a separate Maven install is not required in practice:

```bash
cd bifromq
./mvnw -v
./mvnw -U clean verify -DskipTests -Pbuild-release
```

The output is several archive files with sha512 checksums under /target/output, and the three listed are the source tarball, the binary tarball and a Windows zip. That Windows artefact is a signal about the audience, since a broker for on-premises gateway deployments is expected to run on Windows as well as on Linux.

TLS has a genuine fork in the build. The default binary bundles netty-tcnative-classes only, and if system OpenSSL is unavailable, TLS falls back to JDK TLS automatically. To build against system OpenSSL you add the with-tcnative profile and a platform classifier, with examples given for linux-x86_64, linux-aarch_64, osx-aarch_64, osx-x86_64 and windows-x86_64. There is a parallel with-boringssl-static profile for a BoringSSL static bundle.

That is the level of detail that matters for a broker terminating TLS for a large device fleet. The performance difference between JDK TLS and a native implementation is exactly the kind of thing that shows up at connection rates in the tens of thousands, and the project gives you the switch without requiring you to patch the build.

Testing is split too. ./mvnw test runs unit tests, and ./mvnw test -Pbuild-coverage generates coverage including integration tests and takes longer. The repository has a testsuites/ directory and a coverage-report/ directory for that work.

## Incubating status, release cadence and the licence files that come with it

The release naming makes the incubation explicit. The list shows v4.0.0-incubating published on 2026-01-28, preceded by v4.0.0-incubating-RC3 on 2025-12-30 and RC2 on 2025-12-18. The last push to the repository was on 2026-10-11, and the repository is not archived, so work continues between releases.

Incubation status is not a formality in the Apache process. It means the project has not yet graduated, that its API and behaviour are expected to change, and that the trademark is not yet the ASF's. For an adopter, the practical reading is that you should pin a version and read the release notes before upgrading, and that the extension API in particular is where change is most likely.

The licence situation is thorough, as it is for most Apache projects. The repository carries LICENSE, LICENSE-Binary, NOTICE and NOTICE-Binary, plus a licenses/ directory and a .licenserc.yaml that appears to automate the licence header check. The README itself opens with the standard ASF header block.

The trademark position is worth noting for a product that resells device connectivity. The DISCLAIMER file at the root records the usual position, and the .asf.yaml file holds the ASF project metadata. The distinction between Apache-2.0, which covers the code, and the ASF trademark policy, which governs the name, matters if you intend to market a managed BifroMQ service.

## Conclusion

Adopt BifroMQ if you are running one IoT platform for many customers or many business units and need per-tenant throttling, per-tenant settings and a broker that does not drag in a separate storage cluster, since those are the properties the project is built around. Do not adopt it if you want a stable, graduated Apache project or if your operation runs a mainstream broker without these requirements, because the project is still in incubation and its API is expected to move. Before you deploy, set the container memory limit explicitly, decide whether you need the built-in storage engine or an external one, and read the plugin documentation, since authentication and authorization are extension points rather than built-in features.

## FAQ

### What memory settings does the BifroMQ Docker image need?

You must pass the limit twice, as the Docker memory flag and as the MEM_LIMIT environment variable, for example 2G and 2147483648. If it is not specified, BifroMQ sizes the JVM from the host's physical memory, which can get the Docker process killed by the host Out-of-Memory killer.

### Which MQTT versions and transports does BifroMQ support?

The feature list states full support for MQTT 3.1, 3.1.1 and 5.0 features over TCP, TLS, WS and WSS.

### How do I build a BifroMQ cluster with Docker Compose?

Give each node its own configuration file with a clusterConfig block setting env, host, port and a seedEndpoints list naming all nodes, mount it into /home/bifromq/conf/standalone.yml, put the nodes on a shared bridge network, then run docker compose up -d.

### Does BifroMQ need an external database?

The feature list describes a built-in distributed storage engine optimized for MQTT workloads, with no third-party middleware dependencies. The repository also contains base-crdt and base-hlc modules, which are the convergent-state and logical-clock primitives that engine is built on.

## Sources

- [apache/bifromq on GitHub](https://github.com/apache/bifromq)
- [License: Apache-2.0](https://github.com/apache/bifromq/blob/main/LICENSE)
- [Project website](https://bifromq.apache.org/)
- [README](https://github.com/apache/bifromq/blob/main/README.md)
- [Releases](https://github.com/apache/bifromq/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/apache-bifromq
