The Constrained Application Protocol (CoAP) is a specialized web transfer protocol designed for Internet of Things (IoT) devices and constrained networks. It provides a RESTful model similar to HTTP but is much lighter, using UDP instead of TCP, and features built-in discovery, multicast support, and asynchronous message exchange. For Java developers building IoT solutions, CoAP is an essential technology for M2M (Machine-to-Machine) communication.
This article explores the core concepts of CoAP and demonstrates how to implement both a client and a server in Java using the mature and powerful Eclipse Californium library.
1. Core CoAP Concepts
Before diving into code, it's crucial to understand what makes CoAP different:
- RESTful Design: Like HTTP, it uses methods (GET, POST, PUT, DELETE) and status codes, but it's binary and more compact.
- UDP with Reliability: CoAP runs primarily on UDP but includes a lightweight reliability mechanism with confirmable messages (CON) and acknowledgements (ACK). Non-confirmable messages (NON) are used for non-critical data.
- Low Overhead: Tiny headers compared to HTTP, making it ideal for low-bandwidth networks.
- Observe Pattern: A powerful feature that allows a client to "subscribe" to a resource and receive notifications whenever its state changes, similar to WebSockets but much lighter.
- Discovery: Clients can discover the resources offered by a server by querying
/.well-known/core.
2. Introduction to Eclipse Californium
Eclipse Californium is a Java implementation of CoAP targeted at IoT devices and cloud services. It's the de-facto standard library for CoAP in the Java ecosystem.
Maven Dependency:
To get started, add Californium to your project.
<dependencies> <dependency> <groupId>org.eclipse.californium</groupId> <artifactId>californium-core</artifactId> <version>3.10.0</version> </dependency> <dependency> <groupId>org.eclipse.californium</groupId> <artifactId>californium-proxy2</artifactId> <version>3.10.0</version> </dependency> </dependencies>
3. Building a CoAP Server
A CoAP server exposes resources that clients can interact with. Let's create a server with two resources: a simple hello resource and a sensor resource that supports the Observe pattern.
import org.eclipse.californium.core.CoapResource;
import org.eclipse.californium.core.CoapServer;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.server.resources.CoapExchange;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
public class SimpleCoapServer {
public static void main(String[] args) {
// Create a server listening on the default CoAP port 5683
CoapServer server = new CoapServer();
// Add a simple "hello" resource
server.add(new HelloResource());
// Add a sensor resource that changes over time (for Observe)
server.add(new SensorResource());
// Start the server
server.start();
System.out.println("CoAP Server started on port 5683");
}
/**
* A simple resource that responds to GET requests
*/
public static class HelloResource extends CoapResource {
public HelloResource() {
super("hello"); // Resource identifier: coap://localhost:5683/hello
getAttributes().setTitle("A friendly Hello World resource");
}
@Override
public void handleGET(CoapExchange exchange) {
// Respond with a simple message
exchange.respond(CoAP.ResponseCode.CONTENT, "Hello from the CoAP Server!");
}
}
/**
* A sensor resource that supports the Observe pattern
*/
public static class SensorResource extends CoapResource {
private int temperature = 20;
private Random random = new Random();
private Timer updateTimer;
public SensorResource() {
super("sensor"); // Resource identifier: coap://localhost:5683/sensor
setObservable(true); // Enable clients to observe this resource
getAttributes().setObservable(); // Mark as observable in discovery
getAttributes().setTitle("Temperature Sensor");
// Simulate temperature changes every 5 seconds
updateTimer = new Timer();
updateTimer.schedule(new TimerTask() {
@Override
public void run() {
temperature = 18 + random.nextInt(10); // Random temp between 18-27°C
changed(); // Notify all observing clients
System.out.println("Sensor temperature updated to: " + temperature + "°C");
}
}, 5000, 5000);
}
@Override
public void handleGET(CoapExchange exchange) {
// Respond with the current temperature
String payload = "Temperature: " + temperature + "°C";
exchange.respond(CoAP.ResponseCode.CONTENT, payload);
}
@Override
public void handlePOST(CoapExchange exchange) {
// Allow clients to set the temperature via POST
String requestText = exchange.getRequestText();
try {
// Expecting a simple number in the payload
int newTemp = Integer.parseInt(requestText.trim());
this.temperature = newTemp;
changed(); // Notify observers of the change
exchange.respond(CoAP.ResponseCode.CHANGED, "Temperature set to: " + newTemp + "°C");
System.out.println("Temperature set via POST to: " + newTemp + "°C");
} catch (NumberFormatException e) {
exchange.respond(CoAP.ResponseCode.BAD_REQUEST, "Invalid temperature value");
}
}
@Override
public void handleDELETE(CoapExchange exchange) {
// Clean up resources
if (updateTimer != null) {
updateTimer.cancel();
}
delete(); // Remove this resource from the server
exchange.respond(CoAP.ResponseCode.DELETED, "Sensor resource removed");
}
}
}
4. Building a CoAP Client
Now let's create a client that can interact with our server in three different ways: synchronous call, asynchronous call, and using the Observe pattern.
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapHandler;
import org.eclipse.californium.core.CoapObserveRelation;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import java.util.concurrent.CompletableFuture;
public class SimpleCoapClient {
public static void main(String[] args) throws InterruptedException {
// Synchronous GET request
synchronousGet();
// Asynchronous GET request
asynchronousGet();
// POST request to update sensor value
postRequest();
// Observe resource for changes
observeResource();
// Keep the client running to receive observe notifications
Thread.sleep(60000);
}
private static void synchronousGet() {
System.out.println("\n=== Synchronous GET ===");
CoapClient client = new CoapClient("coap://localhost:5683/hello");
CoapResponse response = client.get();
if (response != null && response.isSuccess()) {
System.out.println("Response Code: " + response.getCode());
System.out.println("Response Text: " + response.getResponseText());
} else {
System.out.println("Request failed: " + (response == null ? "timeout" : response.getCode()));
}
client.shutdown();
}
private static void asynchronousGet() {
System.out.println("\n=== Asynchronous GET ===");
CoapClient client = new CoapClient("coap://localhost:5683/hello");
CompletableFuture<CoapResponse> future = client.get();
future.thenAccept(response -> {
if (response != null && response.isSuccess()) {
System.out.println("Async Response: " + response.getResponseText());
}
client.shutdown();
});
future.exceptionally(throwable -> {
System.err.println("Async request failed: " + throwable.getMessage());
client.shutdown();
return null;
});
}
private static void postRequest() {
System.out.println("\n=== POST Request ===");
CoapClient client = new CoapClient("coap://localhost:5683/sensor");
// Set temperature to 25°C
CoapResponse response = client.post("25", MediaTypeRegistry.TEXT_PLAIN);
if (response != null && response.isSuccess()) {
System.out.println("POST Response: " + response.getResponseText());
} else {
System.out.println("POST failed: " + (response == null ? "timeout" : response.getCode()));
}
client.shutdown();
}
private static void observeResource() {
System.out.println("\n=== Observe Resource ===");
CoapClient client = new CoapClient("coap://localhost:5683/sensor");
CoapObserveRelation relation = client.observe(new CoapHandler() {
@Override
public void onLoad(CoapResponse response) {
System.out.println("Observation: " + response.getResponseText());
}
@Override
public void onError() {
System.err.println("Observation failed");
}
});
// The relation can be used to proactively cancel observation later
// relation.proactiveCancel();
}
}
5. Resource Discovery
One of CoAP's powerful features is resource discovery. Let's see how a client can discover what resources a server offers.
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.LinkFormat;
import java.util.Set;
public class CoapDiscoveryClient {
public static void main(String[] args) {
CoapClient client = new CoapClient("coap://localhost:5683/.well-known/core");
CoapResponse response = client.get();
if (response != null && response.isSuccess()) {
String linkFormat = response.getResponseText();
System.out.println("Discovered resources:");
System.out.println(linkFormat);
// Parse the link format
Set<String> links = LinkFormat.parse(linkFormat);
for (String link : links) {
System.out.println(" - " + link);
}
}
client.shutdown();
}
}
6. Advanced Configuration
Californium allows extensive configuration for different network environments:
import org.eclipse.californium.core.network.config.NetworkConfig;
public class ConfiguredCoapServer {
public static void main(String[] args) {
// Create custom network configuration
NetworkConfig config = NetworkConfig.createStandardWithoutFile()
.setInt(NetworkConfig.Keys.ACK_TIMEOUT, 2000) // 2 second ACK timeout
.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 3) // Retry 3 times
.setInt(NetworkConfig.Keys.EXCHANGE_LIFETIME, 30000) // 30 second exchange lifetime
.setInt(NetworkConfig.Keys.PROTOCOL_STAGE_THREAD_COUNT, 2); // Thread pool size
CoapServer server = new CoapServer(config);
server.add(new HelloResource());
server.start();
System.out.println("Configured CoAP server started");
}
}
7. Best Practices and Security
Security (DTLS):
For production systems, CoAP should be secured with DTLS (Datagram Transport Layer Security). Californium supports DTLS with pre-shared keys, raw public keys, and certificates.
// Basic DTLS configuration example // import org.eclipse.californium.scandium.config.DtlsConnectorConfig; // DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); // dtlsConfig.setAddress(...); // Configure PSK, RPK, or certificate stores
Best Practices:
- Use Observe for monitoring instead of frequent polling.
- Choose appropriate message types: CON for critical data, NON for frequent sensor readings where occasional loss is acceptable.
- Implement proper error handling and timeouts.
- Use block-wise transfer for large payloads.
- Consider resource discovery to build dynamic IoT applications.
Conclusion
Implementing CoAP in Java with Eclipse Californium provides a robust foundation for building efficient IoT applications. The protocol's lightweight nature, combined with features like the Observe pattern and resource discovery, makes it ideal for constrained environments. While this article covered the fundamentals, Californium offers many advanced features like block-wise transfer, DTLS security, and proxy support for building enterprise-grade IoT solutions.
The combination of Java's portability and CoAP's efficiency creates a powerful platform for developing everything from tiny embedded devices to large-scale cloud services in the IoT ecosystem.
Further Reading: