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:
HttpServerto set up the server itself;InetSocketAddress/InetAddressto define the server IP address and port;HttpContextto establish the mapping between the URI path and theHttpHandlerclass;HttpHandlerto handle requests;HttpExchangeto 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/directoryIf 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 to200(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 theHeadersclass usesHashMap<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:
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:
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!