Networking
CoAP — Constrained Application Protocol
An application-layer protocol (RFC 7252) built on top of UDP, designed for small, battery-powered, memory-constrained devices talking over lossy networks. It borrows HTTP's REST model — resources, URIs, GET/POST/PUT/DELETE — but re-encodes everything into a compact binary format and adds its own lightweight reliability on top of UDP's unreliable delivery.
- What it is
- A REST-style protocol (GET/POST/PUT/DELETE) over UDP, purpose-built for constrained devices.
- Mental model
- HTTP's request/response idea, re-encoded into a tiny binary format and given just enough optional reliability, not TCP's full guarantee.
- Key numbers
- 4-byte fixed header (vs. HTTP's 100+ bytes of text); CON messages retry until ACKed or give up.
- Reach for it when
- A constrained device needs REST-style request/response but can't afford TCP's connection state or handshake cost.
Why not just use HTTP?
HTTP over TCP works fine on constrained devices in principle, but three things make it a poor fit for the smallest nodes: TCP's connection setup and header overhead per request, HTTP's verbose text-based headers, and TCP's requirement to hold open connection state per peer — expensive in RAM on a device that might have only a few KB to spare. CoAP keeps the REST *model* everyone already knows, but drops the transport and encoding that made it heavy.
Message format
Every CoAP message starts with a fixed 4-byte binary header — contrast with an HTTP request line plus headers, which is easily 100+ bytes of text. Laid out byte by byte, a full message looks like this:
0xFF marker byte is what tells a parser "options end here, payload starts."
| Field | Size | Purpose |
|---|---|---|
| Ver | 2 bits | Protocol version. |
| Type | 2 bits | CON, NON, ACK, or RST (see below). |
| TKL | 4 bits | Length of the token that follows (used to match responses to requests). |
| Code | 8 bits | Method (e.g. GET) on a request, status (e.g. 2.05 Content) on a response. |
| Message ID | 16 bits | Detects duplicates and pairs ACKs with the message they acknowledge. |
After the header: an optional token, a set of TLV-encoded options (the CoAP equivalent of HTTP headers — URI-Path, Content-Format, Observe, etc.), and an optional payload.
CoAP over UDP: how a packet is actually sent
CoAP has no transport of its own — no connection, no streaming, no chunking beyond what Block-wise transfer (further down this page) explicitly implements. Every CoAP message — the entire 4-byte header plus token, options, and payload laid out above — is sent as the whole payload of exactly one UDP datagram, nothing more and nothing less. There's no "opening a CoAP connection" anywhere in this picture — a client just builds those bytes in memory and hands them to the operating system's ordinary UDP socket API, the same one any other UDP program would use.
Step by step, sending one CoAP request looks like this:
- The client builds the entire CoAP message in memory — header, token, options, and payload — exactly as laid out in Message format above.
- It makes one ordinary UDP send call (conceptually
sendto(socket, coap_bytes, server_ip, 5683)) — no handshake, no connect step, no "opening" anything first. - The OS's UDP layer prepends its own 8-byte header (source port, destination port 5683, length, checksum) and hands the result down to IP.
- IP prepends a 20-byte header (source/destination address, protocol number 17 for "this is UDP") and hands it to the link layer.
- Ethernet or Wi-Fi wraps that in a frame and puts it on the wire — the CoAP bytes themselves never change from this point until they're unwrapped at the other end.
- On arrival, the server's network stack strips Ethernet's header, then IP's, then UDP's — in reverse order — and delivers exactly the original CoAP bytes to whichever socket is bound to port 5683.
- The server reads the datagram in one call — UDP is message-oriented, so one
recvfrom()returns one complete message, never a partial fragment of it — and parses the first byte as Ver/T/TKL, same as Message format described. - If the request was CON, the server builds a reply CoAP message and sends it back the exact same way: a brand-new, independent UDP datagram addressed to the client's address and port — not a "reply" riding some existing connection, because there isn't one.
Every request and every response is its own standalone UDP datagram — the OS doesn't remember anything about a CoAP "session" between them. That's exactly why CoAP needs its own Message ID and Token fields (next section) to match a response back to the request that caused it. TCP gets that matching for free from its connection state; CoAP has to rebuild a thin version of it at the application layer instead, because UDP hands it nothing to work with.
In pure packet count, this is the entire cost of one CoAP exchange: one UDP datagram out, one back — zero back for a NON message. Compare that to HTTP over a freshly-opened TCP connection — SYN, SYN-ACK, ACK, then the HTTP request, then the response, then a FIN/FIN-ACK teardown — six or more packets before either side is even done, which is exactly the overhead Why not just use HTTP? above was pointing at.
Reliability on top of UDP
Raw UDP (see UDP) gives you no delivery guarantee at all. CoAP adds a thin, optional reliability layer using its message type field, without pulling in anything like TCP's connection state:
- CON
- Confirmable — the sender retransmits (with exponential backoff) until an ACK arrives or retries are exhausted.
- NON
- Non-confirmable — fire-and-forget, no retransmission; used for data where a missed update doesn't matter (e.g. periodic sensor readings).
- ACK
- Acknowledges a CON message, and — since CoAP is request/response — usually piggybacks the actual response in the same packet.
- RST
- Sent when a message can't be processed (e.g. the receiver has no state for it) — tells the sender to stop.
This is deliberately much simpler than TCP's sliding windows and congestion control — CoAP only needs "did the other end get this request/response," not a general-purpose reliable byte stream.
Message exchanges, frame by frame
The tables above describe fields; these diagrams show what actually goes out on the wire and what comes back, for the three exchanges that come up constantly. CON, ACK, and NON below are colored consistently with the message-type field they represent.
REST semantics: methods and response codes
CoAP requests target a resource identified by a URI path, using the
same method vocabulary as HTTP: GET, POST,
PUT, DELETE. Response codes reuse HTTP's
class structure but pack the class and detail into a single byte,
written as class.detail:
| CoAP code | Meaning | HTTP equivalent |
|---|---|---|
| 2.05 Content | Success, response has a body | 200 OK |
| 2.01 Created | Resource created | 201 Created |
| 4.04 Not Found | No such resource | 404 Not Found |
| 4.05 Method Not Allowed | Method doesn't apply to this resource | 405 Method Not Allowed |
| 5.00 Internal Server Error | Server-side failure | 500 Internal Server Error |
Observe and block-wise transfer
Two extensions matter enough in practice to know by name:
- Observe (RFC 7641) — a client GETs a resource with an Observe option set, and the server keeps sending updated representations whenever the resource changes, without the client re-requesting. It turns plain request/response into a lightweight publish/subscribe pattern, without needing a broker.
- Block-wise transfer (RFC 7959) — splits a payload too large for one datagram into a sequence of numbered blocks, since constrained nodes and paths often can't reliably move large single UDP payloads.
GET with Observe=0 turns into an ongoing subscription — the server pushes, the client just acknowledges.CoAP vs. raw UDP vs. MQTT vs. HTTP
| Raw UDP | CoAP | MQTT | HTTP | |
|---|---|---|---|---|
| Transport | UDP | UDP (usually) | TCP | TCP |
| Model | You define everything | REST (request/response) | Publish/subscribe via broker | REST (request/response) |
| Reliability | None built-in | Optional, per-message (CON/ACK) | Broker-mediated QoS levels | TCP's (always reliable) |
| Header overhead | 8 bytes | 4-byte fixed header + compact options | 2-byte fixed header + variable | ~100s of bytes, text |
| Connection state | None | None required | Persistent TCP connection per client | Per-connection (or per-request) |
| Multicast support | Yes (UDP) | Yes — a defined use case | No (TCP is point-to-point) | No |
| Typical use | Custom minimal protocols, telemetry | Constrained-device REST APIs, device control | Many-to-many sensor/event fan-out | General web APIs, non-constrained clients |
When to choose what
- Choose raw UDP when you control both ends completely, need the absolute minimum overhead, and don't need REST semantics, standardized discovery, or interoperability with anything else — you're willing to hand-roll framing and (if needed) reliability yourself.
- Choose CoAP when you want REST-style request/response (GET a sensor value, PUT an actuator state) on a constrained device, want optional reliability without TCP's cost, need multicast discovery on a local network, or want interoperability with other CoAP-speaking devices/tools.
- Choose MQTT when the shape of the problem is really publish/subscribe with many producers and consumers, and a central broker plus TCP's connection overhead is acceptable (i.e. the device isn't as constrained, or already needs a persistent connection anyway). For complex routing rather than flat topics, see AMQP.
- Choose HTTP when the client is a normal web browser or service, constraints don't apply, and you want to use the existing web ecosystem (auth, caching, tooling) as-is.
Pros and cons
| Pros | Cons | |
|---|---|---|
| Raw UDP | Minimal possible overhead; total control; simplest mental model for a fixed, closed system. | No standard semantics — you invent framing, addressing, and reliability yourself, and re-invent bugs others already fixed; zero interoperability. |
| CoAP | Small, fast, low-power; REST semantics developers already know; optional reliability; native multicast/discovery; DTLS (CoAPs) or OSCORE for security. | Still UDP underneath, so NAT traversal and firewall friendliness are worse than TCP-based options; smaller ecosystem/tooling than HTTP; security is an added layer, not built in by default. |
Real-world applications
- Smart lighting and building automation — CoAP's REST model (GET a sensor, PUT a light state) maps directly onto controlling building devices from a central system.
- Battery-powered sensor nodes — Observe (see above) lets a low-power sensor push updates without the server having to poll it constantly.
- LPWAN deployments (e.g. over 6LoWPAN) — CoAP is a common application-layer choice on low-power wireless mesh networks where every byte of overhead costs battery life.
- Firmware/config management on constrained devices — block-wise transfer makes it practical to push a configuration update or small firmware chunk over a link that can't carry one large UDP payload reliably.
Gotchas
- CON retransmission has its own timeout/backoff — don't also build a duplicate reliability layer above it, or timeouts compound.
- The Message ID (for dedup/ACK matching) and the Token (for matching a response to its request) are separate mechanisms with separate purposes — conflating them is a common source of confusing bugs when writing a CoAP client from scratch.
- Plain CoAP is unencrypted, same as plain HTTP — production deployments need DTLS (CoAPs, like UDP's answer to TLS) or OSCORE, not "add it later."
- Many corporate/carrier firewalls block or heavily rate-limit UDP more aggressively than TCP:443 — a real deployment constraint UDP-based protocols share, CoAP included.
CoAP's real trick isn't "REST over UDP" by itself — plenty of things run over UDP. It's putting just enough of TCP's useful behavior (optional reliability, dedup, request/response correlation) back on top of UDP as an application-layer concern, so a constrained device only pays for the guarantees it actually needs on a per-message basis, instead of a connection-wide all-or-nothing guarantee like TCP gives you.