go-coap: a CoAP server and client for constrained networks
Implementation of CoAP Server & Client in Go
At a glance
- What is it?
- go-coap implements the Constrained Application Protocol in Go, with servers and clients over UDP, TCP, TCP-TLS and DTLS, and support for observe, block-wise transfers, multicast and the NoResponse option. The RFCs are named in the feature list, which is a reasonable signal that the implementation was checked against the specification.
- Who is it for?
- go-coap fits a Go team building battery-powered sensors or building automation where a datagram transport and the CoAP options are worth the extra work over HTTP. It is the wrong choice for a device that can afford TLS over TCP to a server you control, and the wrong choice if you need the CoAP ecosystem's interop guarantees with devices from other vendors.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A CoAP server and client for constrained devices, in Go
go-coap is an implementation of the Constrained Application Protocol, a specialised web transfer protocol for constrained nodes and constrained networks in the Internet of Things. The protocol targets machine-to-machine applications such as smart energy and building automation, which is the context that matters for judging the library. CoAP is not HTTP with a smaller header. It is a binary protocol designed for devices that sleep, radio links that drop packets, and networks where a request has to fit in one datagram. The library provides servers and clients for DTLS, TCP-TLS, UDP and TCP, it is Apache-2.0 licensed, and the releases v3.5.3, v3.5.4 and v3.5.5 landed in June and October 2026 with the last push on 2026-10-20. The module path is `github.com/plgd-dev/go-coap/v3` and it needs Go 1.25 or higher.
The feature list is a list of RFCs, which is a good sign
The features section is written as a list of specifications, and that is the most informative thing about the project. CoAP over UDP per RFC 7252. CoAP over TCP and TLS per RFC 8232. Observe resources per RFC 7641, which is the mechanism for a client to be told when a resource changes rather than polling it. Block-wise transfers per RFC 7959, which is how a payload larger than a datagram is transferred. The CoAP NoResponse option per RFC 7967, which lets a client ask the server not to answer at all, a real saving on a constrained link. The Too Many Requests response code per RFC 8516. On top of that, a request multiplexer, multicast, and CoAP over DTLS built on pion/dtls. A library that names the RFCs it implements is telling you it has been checked against the spec rather than against one vendor's device.
The server is a router with middleware, and the ports are the same for every transport
The simple sample shows the shape of the server API, and the first half is a middleware and a handler:
%s
The `main` function then wires them into a router and listens:
%s
The commented alternatives are worth noticing because they show that the transport is a parameter rather than a separate API. `coap.ListenAndServe("udp", ":5688", r)` serves UDP, and the commented lines show the TCP, TCP-TLS and two DTLS variants using the same port number and the same router. The `mux.Message` and `mux.ResponseWriter` types are transport-agnostic, so a handler written once works over any of them. The middleware pattern is the standard net/http one adapted, with `ServeCOAP` in place of `ServeHTTP`.
The client mirrors the server, and the same dialer picks the transport
The client sample is the other half:
%s
`udp.Dial("localhost:5688")` produces a client, and the commented lines show `tcp.Dial`, `tcp.Dial` with `tcp.WithTLS`, and the two DTLS forms. The request is then a method call with a context, `co.Get(ctx, "/a")`, and the response is a message. The context timeout in the example is one second, which is a reasonable default for a constrained link but is worth thinking about when a block-wise transfer is involved, since the whole transfer has to fit inside the deadline the caller sets.
DTLS is moving to an options-based API, and the old form is on notice
The DTLS section carries an explicit deprecation notice. `*dtls.Config` is deprecated in pion/dtls v3 in favour of immutable options-based configurations, and its use in go-coap is consequently deprecated as well and may be removed in a future major version. The migration target is `net.NewDTLSServerOptions` and `dtls.NewDTLSClientOptions`:
%s
Both approaches are currently accepted by the same generic functions, `ListenAndServeDTLS`, `Dial` and `NewDTLSListener`, so an existing codebase is not broken today. The new form composes configuration from individual options, with a PSK callback and an explicit cipher suite, and the sample uses `TLS_PSK_WITH_AES_128_CCM_8`, which is one of the cipher suites that suit constrained devices. The `dtls/` directory at the repository root holds this package's own wrapper around pion, and the examples directory has an `options/` folder with the complete server and client.
The full options-based example is:
import (
coap "github.com/plgd-dev/go-coap/v3"
coapnet "github.com/plgd-dev/go-coap/v3/net"
coapdtls "github.com/plgd-dev/go-coap/v3/dtls"
piondtls "github.com/pion/dtls/v3"
)
// Server
serverOpts := coapnet.NewDTLSServerOptions(
piondtls.WithPSK(func(hint []byte) ([]byte, error) {
return []byte{0xAB, 0xC1, 0x23}, nil
}),
piondtls.WithCipherSuites(piondtls.TLS_PSK_WITH_AES_128_CCM_8),
)
log.Fatal(coap.ListenAndServeDTLS("udp", ":5688", serverOpts, r))
// Client
clientOpts := coapdtls.NewDTLSClientOptions(
piondtls.WithPSK(func(hint []byte) ([]byte, error) {
return []byte{0xAB, 0xC1, 0x23}, nil
}),
piondtls.WithCipherSuites(piondtls.TLS_PSK_WITH_AES_128_CCM_8),
)
co, err := coapdtls.Dial("localhost:5688", clientOpts)Where go-coap is the wrong tool
Three limits are concrete. The first is the constraint itself. CoAP is a good fit for battery-powered sensors and LAN automation, and a poor fit for a service that wants to sit behind a normal HTTP proxy, reuse existing web tooling or integrate with a browser, and if your device has enough power for TCP and TLS to a server you control, plain HTTPS is usually less work than a CoAP stack with its own block-wise transfer semantics. The second is the DTLS transition, which is a live migration rather than a settled state. The old `*dtls.Config` form still works but is slated for removal in a future major version, so a project starting today should use the options API from the beginning, and a project on the old form has work ahead. The third is the module's maturity in the v3 line, where the go.mod pins pion/dtls v3.1.5 and the whole library depends on that interface being stable.
The alternative is an HTTP library, and the difference is the datagram
The realistic alternative for a Go IoT service is net/http over TLS, or an MQTT broker for the publish-subscribe case. The difference in approach is the transport model underneath everything. CoAP over UDP is a datagram protocol, which is what makes the NoResponse option and the Observe mechanism cheap: a notification is one small datagram rather than a held-open connection, and a request that does not need an answer costs one packet in one direction. HTTP is a stream protocol, and every one of those savings has to be rebuilt with long-polling or a websocket. MQTT sits in between, with a broker absorbing the connection management, at the cost of a broker to operate and a protocol that does not carry the same request-response shape. The trade is that a CoAP stack is more work to get right in the first place, and it buys you a smaller, more predictable radio footprint.
Editorial conclusion
go-coap fits a Go team building battery-powered sensors or building automation where a datagram transport and the CoAP options are worth the extra work over HTTP. It is the wrong choice for a device that can afford TLS over TCP to a server you control, and the wrong choice if you need the CoAP ecosystem's interop guarantees with devices from other vendors. Before you adopt it, decide which DTLS configuration style you will use, since the `*dtls.Config` form is deprecated in favour of the options-based API and may be removed in a future major version, and check that the Go 1.25 floor matches your toolchain.
Frequently asked questions
Which transports does go-coap support?
CoAP over UDP per RFC 7252, over TCP and TLS per RFC 8232, and over DTLS built on pion/dtls. The same router and handler code serves any of them, with the transport chosen at ListenAndServe or Dial time.
What Go version does go-coap need?
Go 1.25 or higher. The module is `github.com/plgd-dev/go-coap/v3` and the go.mod declares go 1.25.0, with pion/dtls v3.1.5 as the DTLS dependency.
What is the deprecation notice about DTLS in go-coap?
`*dtls.Config` is deprecated in pion/dtls v3 in favour of immutable options-based configurations, and its use in go-coap is deprecated too and may be removed in a future major version. Both forms are currently accepted by ListenAndServeDTLS, Dial and NewDTLSListener, so existing code still works.
How do I write a CoAP server with go-coap?
Create a router with `mux.NewRouter()`, register handlers with `r.Handle("/a", mux.HandlerFunc(handleA))`, add middleware with `r.Use(...)`, and serve it with `coap.ListenAndServe("udp", ":5688", r)`. Handlers receive a `mux.ResponseWriter` and a `*mux.Message` and set the response with `w.SetResponse`.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/plgd-dev-go-coap)Was this useful?