Skip to Content
Course content

144: Building a Basic HTTP Server

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was tasked with debugging a fleet of embedded devices that were supposed to push telemetry data to a central server. The problem was that the devices were intermittently failing, and I had no way of knowing if they were even attempting to connect. I didn't want to spend three hours configuring a full-blown Spring Boot application or deploying a cloud instance just to see if a packet was hitting a port. I needed something I could launch from my IDE in five seconds that would simply log the incoming requests and send a "Got it" response. That's when I leaned on the built-in com.sun.net.httpserver package. It's not meant for production-grade high-traffic sites, but for internal tooling, diagnostics, or lightweight APIs, it's a lifesaver.

Wiring up the Server Socket

In Java, you don't actually need an external library to get a web server running. The JDK comes with a lightweight HttpServer class. To get started, you use the create() method, which takes an InetSocketAddress. I usually suggest using a port like 8080 or 8000 to avoid the privileged port restrictions (anything under 1024 usually requires root/admin access) that can trip you up during development.

HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);

That second argument, the backlog, tells the server how many incoming connections to queue up before it starts rejecting them. Setting it to 0 lets the system handle it automatically. But the server is just a shell until you give it "contexts"—essentially mapping a URL path to a specific piece of logic.

Creating Custom Handlers for Request Routing

The real work happens in the HttpHandler. Think of this as your controller. When a request hits a specific path, the server hands the HttpExchange object to your handler. This object is everything: it contains the request headers, the input stream for the body, and the output stream you'll use to send the response back to the client.

I've found that the most common mistake here is forgetting to close the exchange or failing to set the response headers before writing to the body. If you don't specify the content length, the client might just hang, waiting for more data that's never coming. Here is how I typically structure a handler to return a simple system status message:

server.createContext("/status", exchange -> {
    String response = "{\"status\": \"Online\", \"uptime\": \"24h\"}";
    exchange.getResponseHeaders().set("Content-Type", "application/json");
    exchange.sendResponseHeaders(200, response.length());
    
    try (var os = exchange.getResponseBody()) {
        os.write(response.getBytes());
    }
    exchange.close();
});

By using a lambda here, we keep the code concise. Note that we set the header to application/json—this tells the browser or the calling device exactly how to interpret the bytes we're sending. Once your contexts are mapped, you call server.start(), and your machine is officially listening for requests.

Managing the Executor for Concurrency

By default, the HttpServer handles requests in a way that can be quite limiting. If you have a handler that does something slow—like querying a database or hitting another API—it can block other incoming requests. To fix this, you should assign an Executor to the server. I typically use a cached thread pool so that the server can spawn new threads as demand increases rather than processing everything sequentially.

server.setExecutor(Executors.newCachedThreadPool());

Adding this one line transforms the server from a simple diagnostic tool into something that can actually handle a handful of concurrent users without breaking a sweat. It's a small detail, but it's the difference between a tool that works and a tool that freezes the moment you open two browser tabs.




📋 Practical Task

Building a Local System Health Dashboard

Your goal is to create a standalone Java application that runs a local HTTP server on port 8081. The server must implement two distinct endpoints:

  • /health: Should return a plain text response saying "System Healthy" with an HTTP 200 status code.
  • /metrics: Should return a JSON string containing a simulated memory usage value (e.g., {"heap_used": "256MB", "threads": 12}) with the Content-Type set to application/json.

Ensure that you implement a cached thread pool executor to handle the requests and that each handler properly closes the HttpExchange to prevent memory leaks.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.