15 minutes read

Java developers have quite a lot of different HTTP servers to choose from, such as Tomcat, Jetty, or Spark, but you can also set up a simple server using regular Java APIs from the com.sun.net.httpserver of jdk.httpserver module. In this topic, you'll configure a simple HTTP server that will have its local IP address and port from scratch, and you'll see how to handle requests with it. Additionally, you'll configure a basic authentication for it. All this can be achieved by just using Java classes. So, let's start implementing our server!

Creating an HTTP server

In Java, you can create a simple HTTP server using a small set of classes:

  • HttpServer to set up the server itself;

  • InetSocketAddress/InetAddress to define the server IP address and port;

  • HttpContext to establish the mapping between the URI path and the HttpHandler class;

  • HttpHandler to handle requests;

  • HttpExchange to operate with the request and response data.

Let's create a simple server that will be available at http://127.0.0.1:8080/home or http://localhost:8080/home address:

public class Main {
    public static void main( String[] args ) throws Exception {
        // Create HttpServer which is listening to the given port on the given IP address
        InetAddress localAddress = InetAddress.getByName("127.0.0.1");
        HttpServer server = HttpServer.create(new InetSocketAddress(localAddress, 8080), 0);

        // Create a default context
        HttpContext context = server.createContext("/home", new MyHttpHandler());
        // You can also set up the handler to process requests with the setter:
        // context.setHandler(new MyHttpHandler());

        // Start the server
        server.start();
    }
}

class MyHttpHandler implements HttpHandler {
    @Override
    public void handle(HttpExchange exchange) throws IOException {
        String response = "Hello, Students!";

        // Response status and length
        exchange.sendResponseHeaders(200, response.length());

        // Output the response 
        try (OutputStream stream = exchange.getResponseBody()) {
            stream.write(response.getBytes());
        }
    }
}

With this code, you can make a GET request and get a Hello, Students! as a result.

As you can see, the handle() method processes the request by accepting an HttpExchange object that contains the request data. Let's move to the next section and take a closer look at this class.

Simple web server using command line

Java 18 introduced the jwebserver tool that lets you easily set up a minimal HTTP static file server. It is lightweight, with no dynamic content handling, and is especially useful for prototyping, testing, educational exercises, and ad-hoc coding.

To start a server, use this command:

jwebserver 

This command creates a server that binds to address 127.0.0.1 and uses port 8000 by default. It serves files from your current working directory, which becomes the root of the web server.

You can also use command line options/flags to specify address, port, and directory for the server using flags -b, -p, and -d respectively. For example:

jwebserver -b 127.0.0.1 -p 9000 -d /path/to/your/directory

If the server setup succeeds, you will get the following output in the terminal:

Serving /path/to/your/directory and subdirectories on 127.0.0.1 port 9000
URL http://127.0.0.1:9000/

The server runs in the foreground, which means the particular terminal session will be occupied until the server is stopped. To stop the server, you can send an interrupt signal called SIGINT by pressing CTRL+C. This will cause the server to immediately stop running and release the port it was using.

HttpExchange class methods

Before handling the request, you must know its type: GET, POST, PUT, and so on. For this purpose, this class has a getRequestMethod() that returns the request type. So, you need to find the request type and handle it properly:

@Override
public void handle(HttpExchange exchange) throws IOException {
    System.out.printf("Accepted a %s request\n", exchange.getRequestMethod());

    if (exchange.getRequestMethod().equals("GET")) {
        handleGetRequest(exchange);
    } else if (exchange.getRequestMethod().equals("POST")) {
        handlePostRequest(exchange);
    }
               /* Other request types */
}

Now, let's implement the handleGetRequest() and handlePostRequest() methods and see how HttpExchange methods are involved in this process:

public Headers handleGetRequest(HttpExchange exchange) throws IOException {
    String response = "Hello, " +  exchange.getRequestURI().toString().split("=")[1];

    // Response code and length
    exchange.sendResponseHeaders(200, response.getBytes().length);
    // Set additional response header
    exchange.getResponseHeaders().set("Access-Control-Allow-Origin", "*");

    try (OutputStream stream = exchange.getResponseBody()) {
        stream.write(response.getBytes());
    }

    return exchange.getResponseHeaders();
}

Since in GET requests passing parameters in the URI is a common practice, the first method we'll use is the getRequestURI() method. In this case, it will work for the /home?param=value syntax. You'll get the request URI and find the parameter value using the split() method. For instance, if your URI is http://127.0.0.1:8080/home?name=James, the output of this request written to the OutputStream of the getResponseBody() will be Hello, James.

Each HTTP response includes headers that describe information about it. The code above uses two HttpExchange methods:

  • sendResponseHeaders() to set the response status to 200(OK) and the length of the response content.

  • getResponseHeaders() to set the desired header and return it together with the two headers mentioned above. Note that the Headers class uses HashMap<String, List<String>> to store headers.

This is all you need to understand how to process GET requests. Now, let's explore the handlePostRequest() method:

public Headers handlePostRequest(HttpExchange exchange) throws IOException {
    String response = "";

    try (BufferedReader reader = new BufferedReader(new InputStreamReader(exchange.getRequestBody()))) {
        while(reader.ready()) {
            response += reader.readLine();
        }
    }

    response = "Hello, " + response.split(":")[1].replaceAll("[^a-zA-Z]", "");

    // Prints "Content type: [application/json]"
    System.out.println("Content type: " + exchange.getRequestHeaders().get("Content-type"));

    exchange.sendResponseHeaders(200, response.length());

    try (OutputStream stream = exchange.getResponseBody()) {
        stream.write(response.getBytes());
    }

    return exchange.getResponseHeaders();
}

In a POST request, you send the required data in a request body, so, as a first step, we use the getRequestBody() method to get the JSON content from our request body. In the code above, you can transform the data with one parameter into a similar string: { "property": "value"} in a try-with-resources block. Later, the split() method divides it into two parts where the colon symbol is, returning those two parts of the string as an array, and we take the second one to remove everything except for alphabet letters.

Both operations that get the required parameter value using regular expressions in the split() method could be handled with Pattern and Matcher classes. We used the split() method to keep things simple. Try to replace the existing solutions to get the required data using the two mentioned classes. We'll be glad to see your solutions in the comments section.

In Postman, your request with one parameter in a request body can be like this:

Handle Post Request in Postman

You can implement methods to handle other HTTP requests such as PUT or DELETE, and others in the same way, so we won't focus on it in this topic. Instead, we'll talk about other important features you can add to your server.

In the image above, we send JSON data to set the name value to James. This information can be found in the request headers because, just like responses, requests also have headers and you can access them with the getRequestHeaders() method.

If you're using Java 21 or later, be aware that HTTP header size limits are now enforced by default. This can affect how your server handles requests that include large cookies, authorization tokens, or custom headers—particularly in production environments where headers may grow unexpectedly.

As of JDK 21, the built-in HttpServer implementation introduced a default limit of 384kB (393,216 bytes) for the cumulative size of all request headers. This includes the sum of all header names and values, plus an additional 32 bytes of overhead per header pair. If this limit is exceeded, the connection is silently closed by the server, and your request will not be processed. The default value of the limit can be changed by specifying a positive value with the jdk.http.maxHeaderSize system property on the command line. You can look into this and other system properties of HttpServer in the official documentation.

Authentication support with BasicAuthenticator

One of the most important features each server must have is authentication: You can use the BasicAuthenticator class here for that purpose:

public static void main( String[] args ) throws Exception {
    // Create HttpServer which is listening on the given port on the given IP address
    InetAddress localAddress = InetAddress.getByName("127.0.0.1");
    HttpServer server = HttpServer.create(new InetSocketAddress(localAddress, 8080), 0);

    // Create a default context
    HttpContext context = server.createContext("/home", new MyNewHttpHandler());
    context.setAuthenticator(new BasicAuthenticator("realm") {
        @Override
        public boolean checkCredentials(String user, String password) {
            return user.equals("admin") && password.equals("Java!1995");
        }
    });

    //Start the server
    server.start();

    System.out.println("Server started");
}

The code above sets a username and a password for requests. After this, if you're doing your requests in Postman, you'll need to configure your username and password in the Authorization tab to process POST requests:

Set Basic Auth in Postman

Note that you don't need to change anything in the handlePostRequest() method after adding the authentication configuration.

Configuring the request handling process

In this section, you'll learn about two important configurations you can make for your server. The first one is setting a backlog argument in the HttpServer.create() method. When working with the HttpServer class, requests are placed in a queue, and if it's full, the server refuses to add a new request to the queue. If the backlog value is zero then you don't have any limits for it. In the example below, the backlog value is 1000 and more requests will be declined.

public static void main( String[] args ) throws Exception {
    // Create HttpServer which is listening on the given port on the given IP address
    InetAddress localAddress = InetAddress.getByName("127.0.0.1");
    HttpServer server = HttpServer.create(new InetSocketAddress(localAddress, 8080), 1000);
    server.setExecutor(Executors.newFixedThreadPool(10));

    // Create a default context
    HttpContext context = server.createContext("/home", new MyNewHttpHandler());

    //Start the server
    server.start();

    System.out.println("Server started");
}

Another important line you might notice is server.setExecutor(Executors.newFixedThreadPool(10)); which sets an executor that handles requests in tasks in order to handle multiple requests simultaneously. If you don't set an executor, requests will be handled by the background thread created by the start() method.

Conclusion

In this topic, you configured a small HTTP server and learned how to handle requests. You practiced with different HttpExchange methods you'll use to operate with request and response data and configured a basic authentication for your server. Hope this will be useful for you one day!

9 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo