Java 18's Simple Web Server lets you use a command-line tool or API to host files and more. Here's how it works. Credit: Thinkstock One of the handiest new features included in the Java 18 release (March 2022) was the new Simple Web Server, which makes it easy to spin up and configure an HTTP file server. It also exposes an API that extends the existing httpserver package for building simple use cases. The new Simple Web Server is a useful tool that every Java developer should have in their bag of tricks. Let’s check it out! Java’s Simple Web Server on the command line Java’s new jwebserver command makes it simple to run a basic web server. It is analogous to the popular SimpleHTTPServer tool in the Python world. The first step is to make sure you are running Java 18 or a later release. Type java --version to find out what release you are currently running. I recommend using SDKMan to manage JDK installs. It’s especially useful for juggling multiple versions. jwebserver basics The most basic thing you can do with the Java Simple Web Server is to serve the current directory on port 8000. Just enter the command shown in Listing 1. Listing 1. No-arg web server $ jwebserver Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::". Serving /home/matthewcarltyson and subdirectories on 127.0.0.1 port 8000 URL http://127.0.0.1:8000/ From there, if you go to your browser and visit localhost:8000, you’ll see a listing of the file system, as shown in Figure 1. IDG Figure 1. A Simple Web Server instance running on port 8000. Configuring the command line There are several common things you might need to do to fine-tune Simple Web Server on the command line. For example, you can change the port, the address to bind to (the network interface to listen on), and the directory to serve. In Listing 2, you can see how to listen on port 8080, on all interfaces, and in the /foo/bar directory. Listing 2. Listen on port 8080, all interfaces, /foo/bar $ jwebserver -b 0.0.0.0 -p 8081 -d /foo/bar You can get a list of all the options with $ jwebserver -h. As you can see, the jwebserver command-line tool makes it possible to serve static files using the simplest possible syntax. Next, we’ll take a look at the Simple Web Server API. Using Java’s Simple Web Server API The Simple Web Server Javadoc is a good starting point for learning about the API. The SimpleFileServer class exists in the com.sun.net.httpserver package. (This package also houses the older, lower-level APIs for building web servers. The httpserver package extends that functionality for simpler requirements.) The jwebserver CLI tool uses SimpleFileServer to do its work, and we can also use it programmatically. The SimpleFileServer class only handles GET and HTTP/1.1. We can do some interesting things with it, though. For example, this introduction to working with Simple Web Server suggests a way to use the Google Java in-memory file system project to fake a file system for the handler. We’re going to use the idea of an in-memory file system to modify the FileHandler in SimpleFileServer to actually serve a virtual file system from memory. Then, we’ll use the httpserver package to handle a POST to add a faux file to the faux file system. Serve a virtual file system from memory To begin, let’s create a quick Maven project using the following command: $ mvn archetype:generate -DgroupId=.com.infoworld -DartifactId=jsws -DarchetypeArtifactId=maven-archetype-quickstart Now, CD into the new /jsws directory. Set the compiler and source versions to 18 in the pom.xml, as described here. Next, add Google jimfs to the dependencies, as shown in Listing 3. Listing 3. The Google Java in-memory file system dependency <dependency> <groupId>com.google.jimfs</groupId> <artifactId>jimfs</artifactId> <version>1.3.0</version> </dependency> Now, we can modify the src/main/java/App.java file to serve a fake file system. You can see the code to do this in Listing 4. Listing 4. Serving the in-memory file system with SimpleFileServer package com.infoworld; import java.util.List; import java.nio.charset.StandardCharsets; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import com.sun.net.httpserver.SimpleFileServer; import static com.sun.net.httpserver.SimpleFileServer.OutputLevel; public class Mem { private static final InetSocketAddress ADDR = new InetSocketAddress("0.0.0.0", 8080); public static void main( String[] args ) throws Exception { Path root = createDirectoryHierarchy(); var server = SimpleFileServer.createFileServer(ADDR, root, OutputLevel.VERBOSE); server.start(); } private static Path createDirectoryHierarchy() throws IOException { FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); Path root = fs.getPath("/"); Files.createDirectories(root.resolve("test/test2")); Path foo = fs.getPath("/foo"); Files.createDirectory(foo); Path hello = foo.resolve("hello.txt"); Files.write(hello, List.of("hello", "world"), StandardCharsets.UTF_8); return root; } } The idea in Listing 4 is to simulate the standard local file system API using Google’s open source jimfs library, which implements the java.nio.file API but does everything in-memory, like a virtual file system. Using the library, you can define your own directory paths and files programmatically. So, we create our own virtual directory structure and hand that off to SimpleFileServer as the file handler. We configure the SimpleFileServer class programmatically: var server = SimpleFileServer.createFileServer(ADDR, root, OutputLevel.VERBOSE); This accepts the internet address to bind to, just like we saw from the command line. In this case, we pass in the unspecified interface and port 8080. After that comes the file system root. For this example, we’ll give it the Path object created by our createDirectoryHierarchy() method. The createDirectoryHierarchy() method uses jimfs to build a Path object: FileSystem fs = Jimfs.newFileSystem(Configuration.unix());. It then populates the Path with files and directories. The jimfs API for creating paths and files with content is not hard to grasp; for example, we create one with Path hello = foo.resolve("hello.txt");. You can use most of the objects like they were just normal Java NIO paths. Now, if we run this code and visit localhost:8080, you’ll see the directory listing and be able to browse it and see file contents, just like you would with a normal file server. Creating a virtual file Let’s take this idea one step further and add the ability to upload a new file. We can use the com.sun.net.httpserver package to accept a POST request that will upload a new file to our in-memory file system. You can see this in Listing 5. Listing 5. Uploading a new file to the in-memory file system package com.infoworld; import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpExchange; import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import java.util.List; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import com.sun.net.httpserver.SimpleFileServer; import static com.sun.net.httpserver.SimpleFileServer.OutputLevel; public class App { public static void main( String[] args ) throws Exception { // same config... server.start(); // Create the HTTP server with the UploadHandler using the same 'root' path HttpServer httpServer = HttpServer.create(new InetSocketAddress("0.0.0.0", 8081), 0); httpServer.createContext("/upload", new UploadHandler(root)); httpServer.start(); } private static Path createDirectoryHierarchy() throws IOException { // same ... } // Handler to process POST requests and upload files static class UploadHandler implements HttpHandler { private final Path root; public UploadHandler(Path root) { this.root = root; } @Override public void handle(HttpExchange exchange) throws IOException { if ("POST".equalsIgnoreCase(exchange.getRequestMethod())) { String filename = exchange.getRequestHeaders().getFirst("filename"); if (filename != null) { Path newFilePath = root.resolve(filename); try (OutputStream os = Files.newOutputStream(newFilePath)) { exchange.getRequestBody().transferTo(os); } String response = "File uploaded successfully: " + filename; exchange.sendResponseHeaders(200, response.getBytes(StandardCharsets.UTF_8).length); try (OutputStream os = exchange.getResponseBody()) { os.write(response.getBytes(StandardCharsets.UTF_8)); } } else { exchange.sendResponseHeaders(400, -1); // Bad Request } } else { exchange.sendResponseHeaders(405, -1); // Method Not Allowed } } } } In Listing 5, we keep most of the class the same, but at an HttpServer instance listening on port 8081. This is configured with our custom uploadHandler, which takes the uploaded data and uses it to write a new file to the root path that we created in createDirectoryHierarchy. To test this out, we can run the whole server cluster with: $ mvn clean install exec:java -Dexec.mainClass="com.infoworld.Mem" You can push a new file to the server with a CURL request like the one in Listing 6. Listing 6. CURL POST a file $ touch file.txt $ curl -X POST -H "filename: file.txt" -d "@file.txt" http://localhost:8081/upload File uploaded successfully: file.txt When you reload the localhost:8080/ file listings, you’ll see the new file.txt and you can click it and view its contents. A fast, simple, and flexible web server Simple Web Server is a welcome addition to the Java toolset. Not only does it make hosting files very simple with the CLI, it introduces some interesting possibilities with its API, especially when used in conjunction with the lower level HttpServer API. To learn more, check out these additional resources: Java 18’s Simple Web Server: A tool for the command line and beyond Working with the Simple Web Server Related content feature What is Rust? Safe, fast, and easy software development Unlike most programming languages, Rust doesn't make you choose between speed, safety, and ease of use. Find out how Rust delivers better code with fewer compromises, and a few downsides to consider before learning Rust. By Serdar Yegulalp Nov 20, 2024 11 mins Rust Programming Languages Software Development how-to Kotlin for Java developers: Classes and coroutines Kotlin was designed to bring more flexibility and flow to programming in the JVM. Here's an in-depth look at how Kotlin makes working with classes and objects easier and introduces coroutines to modernize concurrency. By Matthew Tyson Nov 20, 2024 9 mins Java Kotlin Programming Languages analysis Azure AI Foundry tools for changes in AI applications Microsoft’s launch of Azure AI Foundry at Ignite 2024 signals a welcome shift from chatbots to agents and to using AI for business process automation. By Simon Bisson Nov 20, 2024 7 mins Microsoft Azure Generative AI Development Tools news Microsoft unveils imaging APIs for Windows Copilot Runtime Generative AI-backed APIs will allow developers to build image super resolution, image segmentation, object erase, and OCR capabilities into Windows applications. By Paul Krill Nov 19, 2024 2 mins Generative AI APIs Development Libraries and Frameworks Resources Videos