IoT & Connected Systems

How to Integrate a Raspberry Pi with a Spring Boot Backend

Zameer Faiz · · 8 min read

The short answer: don't have the Raspberry Pi call your API. Have it publish readings to an MQTT broker, and let your Spring Boot application subscribe to the broker as just another consumer.

The Pi-side code stays tiny. The backend never has to care whether a device is online. And the whole system keeps working through the network dropouts that every real deployment eventually meets.

This guide walks the full path — payload design on the Pi, broker setup, the Spring Integration wiring that turns messages into database rows, and the production details that separate a weekend demo from a system you can leave running. It's the same architecture I run in production on AetherMesh, an asset-tracking platform ingesting live telemetry from 1,000+ tagged assets with sub-second dashboard latency. Every recommendation here has been paid for at least once.

What does the architecture look like?

Five pieces, each with one job — and the data only ever flows one way through them:

Why not plain HTTP from the Pi to a Spring controller? For a single device on reliable Wi-Fi it genuinely works — and if that's your project, build that and ship it.

The design stops scaling the moment devices multiply or connectivity degrades. HTTP makes the device responsible for retries, backoff, and knowing the server's address, and gives you no buffering when the backend restarts. MQTT inverts the responsibility: devices fire-and-forget to a broker designed for flaky networks, quality-of-service levels handle delivery guarantees, and backend deployments become invisible to the field.

How should the Raspberry Pi publish its readings?

Keep the device code boring. A Python script with paho-mqtt, a well-named topic, and a compact JSON payload covers most sensor workloads — the Pi has no business knowing what happens after the broker accepts the message.

publisher.py — on the Raspberry Pi
import json, time
import paho.mqtt.client as mqtt

DEVICE_ID = "pi-greenhouse-01"
client = mqtt.Client(client_id=DEVICE_ID)
client.connect("broker.local", 1883)
client.loop_start()

while True:
    reading = {
        "deviceId": DEVICE_ID,
        "recordedAt": time.time(),   # device time — the backend must trust THIS
        "temperatureC": read_temperature(),
        "humidityPct": read_humidity(),
    }
    client.publish(
        f"sensors/{DEVICE_ID}/environment",
        json.dumps(reading),
        qos=1,                       # at-least-once: dedupe server-side
    )
    time.sleep(30)

Three deliberate choices in that snippet

  1. 1.The payload carries the device's own timestampMessages will arrive late and in bursts, and arrival time is a lie — more on that below.
  2. 2.The topic encodes device identity and reading classsensors/{device}/environment lets the backend subscribe with wildcards, and lets you add device types later without renaming anything.
  3. 3.QoS 1 accepts duplicates, never silent lossThe correct trade for telemetry — as long as the backend deduplicates.

How does Spring Boot consume the messages?

Spring Integration's MQTT support is the well-worn path: an inbound channel adapter subscribes to the topic tree, and a service activator hands each message to your code as it arrives. Add spring-integration-mqtt to the build and the wiring is one configuration class.

MqttIngestionConfig.java — in the Spring Boot backend
@Configuration
public class MqttIngestionConfig {

  @Bean
  public MessageChannel mqttInputChannel() {
    return new DirectChannel();
  }

  @Bean
  public MqttPahoMessageDrivenChannelAdapter inbound() {
    var adapter = new MqttPahoMessageDrivenChannelAdapter(
        "tcp://broker.local:1883", "spring-ingest", "sensors/+/environment");
    adapter.setQos(1);
    adapter.setOutputChannel(mqttInputChannel());
    return adapter;
  }

  @Bean
  @ServiceActivator(inputChannel = "mqttInputChannel")
  public MessageHandler handler(ReadingService readings) {
    return message ->
        readings.ingest((String) message.getPayload());
  }
}

The ReadingService is where the engineering happens. Parse the JSON defensively — field-level validation, not a blind ObjectMapper bind, because firmware bugs will eventually send you garbage. Normalize the timestamp: trust the device's recordedAt, sanity-check it against a plausible window, and deduplicate on device ID plus recorded time so QoS 1's duplicates collapse harmlessly.

Then persist through your normal JPA repository and, if a dashboard needs it live, publish the validated reading onward to a WebSocket topic. From this point down it's ordinary Spring Boot — entities, repositories, a REST controller for history queries. That's precisely the appeal of putting the broker at the boundary: the IoT weirdness stays in one adapter class, and the rest of your backend doesn't know the data came from a device in a field somewhere.

How do you show the data live on a dashboard?

Once readings are flowing into the backend, the last hop is pushing them to a browser without polling. Spring's WebSocket support with STOMP messaging is the shortest path: after persisting a validated reading, the ReadingService publishes it to a topic like /topic/readings/{deviceId}, and a React or Angular dashboard subscribes over a single socket connection.

The pattern to respect is validate-then-broadcast. The dashboard must only ever see readings that survived the same validation as the database — or your live view and your history will quietly disagree, which operators notice immediately and forgive never.

And resist the temptation to have the dashboard subscribe to the MQTT broker directly. It looks like a shortcut — one less hop — but it means shipping broker credentials to browsers, re-implementing validation client-side, and losing the single point where you can throttle, aggregate, or transform the stream. The backend exists to be the one honest narrator between the field and the screen. On AetherMesh, that discipline is what makes sub-second latency compatible with a dashboard you can actually trust.

Should the Pi run Java instead of Python?

It can — a Raspberry Pi runs the JVM happily, and if your team is Java-first there's real appeal in one language across device and backend. In practice I still write the device side in Python for most deployments: startup time and memory matter on constrained hardware, paho-mqtt plus a sensor library is a tiny and mature dependency surface, and device code should stay simple enough that language leverage barely registers.

The place a JVM on the Pi earns its keep is when the device does significant local processing — protocol translation, on-device buffering with real persistence, edge decisions. At that point you're building a gateway, not a sensor node, and the calculus genuinely changes. Choose per role, not per loyalty.

What breaks in production?

Timestamps, first and always. Devices buffer when connectivity drops, then flush in a burst — so readings arrive minutes or hours after they were recorded, out of order, sometimes twice. Every system that stamps rows with arrival time instead of device time eventually shows vehicles teleporting or greenhouses freezing at noon. I've been hired specifically to fix this on a 200-vehicle fleet tracking server, and the fix always lands at the ingestion boundary: trust device time, validate it, dedupe on the device's own identity.

After that, the list is short and predictable. Reconnect behavior on the Pi — paho's loop handles broker reconnects, but your script must survive them without duplicating its publish loop. Disk buffering for readings taken while offline, if the data is too valuable to drop. And security as soon as you leave the bench: TLS on the broker port and per-device credentials, so one compromised device can be revoked without re-provisioning the fleet.

None of this is exotic. It's the same short list every deployment meets, and handling it at design time costs a fraction of retrofitting it after the field data is already corrupted.

Scaling past one Pi

Nothing above changes at 10 or 1,000 devices — that's the point of the broker boundary. What changes is the backend's internals: at higher volumes the ingestion path benefits from reactive processing, and the persistence layer starts caring about time-series query patterns. That's the shape of the production build documented in the AetherMesh case study.

Frequently asked questions

Can Spring Boot talk to a Raspberry Pi directly?

It can — a REST controller receiving POSTs from the Pi works fine for one device on reliable Wi-Fi. The broker architecture in this guide exists for what comes next: multiple devices, flaky networks, and consumers beyond a single API. If you're sure you'll never have those, skip the broker with a clear conscience.

Do I need Spring Integration, or can I use a plain MQTT client?

The Eclipse Paho client works directly in any Java app. Spring Integration earns its place by handling connection lifecycle, threading, and message conversion inside Spring's programming model — one config class instead of hand-rolled reconnect logic. For a Spring Boot backend it's the path of least surprise.

What database should the readings go into?

PostgreSQL is the boring, correct default — with a time-based index it handles telemetry at the scale most projects reach. Dedicated time-series databases earn their complexity at high ingest rates or when retention downsampling becomes a first-class need. Start with Postgres; the migration path is well-trodden if you outgrow it.

How many devices does this architecture scale to?

The architecture doesn't change from 1 device to 1,000 — that's the point of the broker boundary. What changes is the backend's internals: at higher volumes ingestion benefits from reactive processing and the persistence layer starts caring about time-series patterns. AetherMesh runs this exact shape at 1,000+ assets with sub-second latency.

What are the alternatives to MQTT for this pipeline?

Batched HTTPS posts if readings can tolerate minutes of latency and only travel one way — simpler, no broker to run. WebSockets if only browsers are involved. Kafka belongs behind the broker at data-center scale, not on the device side. The MQTT-vs-HTTP decision is covered in depth in the broker guide linked above.

Planning work in this area? The IoT Systems practice page covers scope, engagement models, and pricing signals — or send the brief directly.

Send Project Brief →