Digital Twin

Eclipse Ditto MQTT Tutorial: Open-Source Digital Twins on a Managed Broker

A hands-on Eclipse Ditto MQTT tutorial: run Ditto with Docker, create a thing, add an MQTT 5 connection to a broker, map device JSON into features and publish twin change events back to MQTT.

Eclipse Ditto MQTT setup showing devices publishing to a broker, Ditto maintaining thing twins and events flowing back to dashboards
Run this on your own MQTT brokerGet a private, managed MQTT broker on the Free plan and start from ready-made code samples.
Try it free Code samples No card required

Eclipse Ditto with MQTT is one of the most practical ways to build open-source digital twins: devices publish to a broker, Ditto keeps a JSON twin of every device, and applications read twin state through an API or receive change events. In this tutorial you will run Ditto locally, create a thing, connect Ditto to an MQTT broker, map plain device JSON into twin features and publish twin events back out to MQTT.

For context on where a twin service fits, see the digital twin technology guide.

What Eclipse Ditto Does

Eclipse Ditto is an open-source framework from the Eclipse Foundation for managing digital twins. It does not talk to sensors directly. Instead, it maintains a twin document per device, called a thing, and gives you:

  • A REST and WebSocket API to read and modify twins.
  • Policies that control who can read or write which part of a twin.
  • A connectivity service that connects to external brokers, including MQTT 3.1.1 and MQTT 5.
  • Payload mapping to turn arbitrary device messages into twin updates.
  • Change events and search across all things.

A thing has attributes (mostly static metadata such as location or serial number) and features (functional parts, each with properties for reported values and optionally desiredProperties for target values). This maps neatly onto the reported vs desired pattern described in digital twin state synchronization.

Architecture of an Eclipse Ditto MQTT Setup

devices --publish--> MQTT broker <--subscribe/publish-- Ditto connectivity
                         |                                   |
                         |                             Ditto things (twins)
                         |                                   |
dashboards <--subscribe--+                 REST / WebSocket API for apps

Ditto connects to the broker as an ordinary MQTT client. That means the broker can be anything standards-compliant: a local Mosquitto, the public MQTT test broker for experiments, or a managed MQTT broker such as ConnectMQ in production.

Step 1: Run Eclipse Ditto with Docker

The Ditto repository ships a Docker Compose setup. Clone it and start the stack:

git clone https://github.com/eclipse-ditto/ditto.git
cd ditto/deployment/docker
docker compose up -d

After the containers are healthy, the API is available through nginx at http://localhost:8080. The default development credentials are ditto / ditto for the API and devops / foobar for administrative endpoints such as connections. Change these before exposing Ditto anywhere. The Eclipse Ditto documentation describes the deployment options in detail.

Step 2: Create a Thing

Create a twin for a pump. When you create a thing without an explicit policy, Ditto creates a default policy granting full access to the authenticated subject, which for the nginx setup is nginx:ditto.

curl -u ditto:ditto -X PUT -H "Content-Type: application/json" \
  http://localhost:8080/api/2/things/acme:pump-01 \
  -d '{
    "attributes": { "site": "plant1", "line": "water", "model": "CP-200" },
    "features": {
      "flow": { "properties": { "value": 0, "unit": "l/min" } },
      "pressure": { "properties": { "value": 0, "unit": "bar" } },
      "motor": { "properties": { "running": false } }
    }
  }'

Read it back with curl -u ditto:ditto http://localhost:8080/api/2/things/acme:pump-01.

Step 3: Add an MQTT Connection

Now tell Ditto to connect to the broker. The example below uses MQTT 5 against the public broker with a unique topic prefix. Replace e3b9 with your own random string.

{
  "name": "connectmq-demo",
  "connectionType": "mqtt-5",
  "connectionStatus": "open",
  "uri": "tcp://broker.connectmq.com:1883",
  "specificConfig": {
    "clientId": "ditto-e3b9",
    "separatePublisherClient": "false"
  },
  "sources": [
    {
      "addresses": ["connectmq/demo/e3b9/devices/+/telemetry"],
      "authorizationContext": ["nginx:ditto"],
      "qos": 1,
      "payloadMapping": ["telemetry"]
    }
  ],
  "targets": [
    {
      "address": "connectmq/demo/e3b9/twins/{{ thing:name }}/events",
      "topics": ["_/_/things/twin/events"],
      "authorizationContext": ["nginx:ditto"],
      "qos": 0
    }
  ],
  "mappingDefinitions": {
    "telemetry": {
      "mappingEngine": "JavaScript",
      "options": {
        "incomingScript": "function mapToDittoProtocolMsg(headers, textPayload, bytePayload, contentType) { var name = headers['mqtt.topic'].split('/')[4]; var d = JSON.parse(textPayload); var value = { flow: { properties: { value: d.flow, unit: 'l/min' } }, pressure: { properties: { value: d.pressure, unit: 'bar' } }, motor: { properties: { running: d.running } } }; return Ditto.buildDittoProtocolMsg('acme', name, 'things', 'twin', 'commands', 'merge', '/features', headers, value); }"
      }
    }
  }
}

Save it as connection.json and create it through the connections API with the devops user:

curl -u devops:foobar -X POST -H "Content-Type: application/json" \
  http://localhost:8080/api/2/connections -d @connection.json

Key points in this configuration:

  • separatePublisherClient set to false makes Ditto use one MQTT client for both directions. By default Ditto may open a second client with a derived client ID, which fails on brokers that bind client IDs to issued credentials, as ConnectMQ does for production brokers.
  • The source subscribes to every device's telemetry with a single-level wildcard.
  • The JavaScript mapping extracts the device name from the topic and converts plain JSON into a Ditto merge command on /features, so devices never need to know the Ditto Protocol.
  • The target publishes every twin change event to an events topic per thing.

For production, change the URI to ssl://YOUR_BROKER_HOST:8883, set clientId to YOUR_CLIENT_ID and add the credentials from the ConnectMQ portal, for example ssl://YOUR_USERNAME:YOUR_PASSWORD@YOUR_BROKER_HOST:8883.

Step 4: Publish Device Telemetry

Simulate the pump with mosquitto_pub. The fifth topic level is the thing name that the mapping script extracts:

mosquitto_pub -h broker.connectmq.com -p 1883 -i pump-01-e3b9 -q 1 \
  -t "connectmq/demo/e3b9/devices/pump-01/telemetry" \
  -m '{"flow": 418.5, "pressure": 3.7, "running": true}'

Check the twin:

curl -u ditto:ditto http://localhost:8080/api/2/things/acme:pump-01/features

You should see the new flow, pressure and running values.

Step 5: Consume Twin Events over MQTT

Any application can now follow twin changes without calling the Ditto API. Subscribe to the events topic:

mosquitto_sub -h broker.connectmq.com -p 1883 -i events-viewer-e3b9 \
  -t "connectmq/demo/e3b9/twins/+/events" -v

Publish another telemetry message and a Ditto Protocol event appears, containing the topic acme/pump-01/things/twin/events/merged, the path and the changed value. Dashboards and 3D viewers can subscribe to these events, or connect to Ditto's WebSocket API if they need policy-enforced access per twin.

Production Considerations for Eclipse Ditto and MQTT

Moving from this local setup to production involves a few decisions that are easy to overlook:

  • Credentials and policies. Replace the default nginx users with your identity provider, and create explicit policies per thing or per group of things instead of relying on the default policy. The connection's authorization context should be a dedicated subject with only the permissions it needs.
  • Topic contracts. Document the topic layout the mapping script expects. If device firmware changes the topic depth, the script's index-based extraction breaks silently, so validate inputs and log rejected messages.
  • Throughput. Ditto persists every twin modification. Feeding it every high-rate sample is wasteful; let devices publish on change or at a moderate rate, and send full-rate data to a separate time-series subscriber.
  • Desired state. If applications set desiredProperties, add a target that publishes those changes to a device command topic, and have devices report back through the same telemetry path.
  • Monitoring. Watch connection status and the broker's topic activity together, so you can tell whether a stale twin is caused by the device, the broker or the mapping.

Troubleshooting Eclipse Ditto MQTT Connections

Symptom Likely cause Fix
Connection status stays failed Wrong URI scheme or port Use tcp:// with 1883 or ssl:// with 8883
Connection drops right after connecting Duplicate or unauthorised client ID Set a unique clientId, disable separate publisher client
Messages received but twin not updated Authorization context not in thing policy Match authorizationContext to a subject in the policy
Mapping errors in logs Script throws on unexpected payload Guard against missing fields and invalid JSON
No events on target topic Target topics filter missing Include _/_/things/twin/events

Retrieve connection diagnostics with GET /api/2/connections/{connectionId}/status and /logs after enabling logging through the connection commands endpoint.

When to Use Ditto vs Retained Topics

Ditto adds a twin service with APIs, policies, search and history of changes. If you only need viewers to load current state, retained MQTT topics may be enough and require no extra infrastructure; see digital twin retained MQTT. Choose Ditto when you need queries across many twins, fine-grained access control per twin, desired properties, or a REST API for business applications. For where each fits in a full stack, read digital twin architecture.

Run Eclipse Ditto on ConnectMQ

Ditto handles the twins; ConnectMQ handles the messaging. As a cloud MQTT broker with MQTT 3.1.1 and 5.0, TLS on 8883 and a live topic explorer to watch telemetry and Ditto events side by side, it removes the work of operating your own broker. Sign up at app.connectmq.com, create a broker on the free Serverless plan with no credit card, and plug its credentials into your Ditto connection.

Frequently Asked Questions

Does Eclipse Ditto include an MQTT broker?

No. Ditto connects to an external MQTT broker as a client through its connectivity service, so you choose and operate the broker separately or use a managed one.

Can Ditto use MQTT 5?

Yes. Set connectionType to mqtt-5 for MQTT 5 or mqtt for MQTT 3.1.1.

Do devices need to send Ditto Protocol messages?

No. With a payload mapping such as the JavaScript example above, devices can publish plain JSON and Ditto converts it into twin commands.

How do I send desired values from Ditto to devices?

Update desiredProperties on a feature and add a target that publishes those changes, or send live messages, to a device command topic that the device subscribes to.

Related articles

All articles →

Your private MQTT broker is a few clicks away

Create an account, get your broker credentials and connect your first device — no servers to install, patch or scale.