Introduction

Java 18 made its general availability on March 22nd 2022 (a non-LTS version). One of the features it had was to start a simple web server via the command line to serve static files. In this post, we will learn about the Java 18 simple web server (command-line tool) released as part of JDK 18.

Java 18 Simple Web Server command-line tool

The goal of JEP 408 was to provide a simple command-line tool to launch a minimal web server to serve static files. The motive was to use it for prototyping and to launch a simple web server to serve files quickly. In other words, running the command-line tool will create and start an out-of-the-box HTTP file server. The file server will use some default configuration with command-line options to tweak some.

It was not meant as an alternate or to compete with full-blown production web servers like Apache Tomcat, NGINX etc., Also, it doesn’t have any authentication mechanism, access control or encryption. It was meant to be used for testing, development and debugging.

With this, we don’t have to write Java code to have a web server running or setup a full-blown web server framework.

The simple web server offered via command-line tool is a minimal HTTP server which serves a single directory hierarchy. It is based on the web server implementation in com.sun.net.httpserver package. The simple web server will only support HTTP/1.1 and there is no HTTPS support.

Directory organization and structure

For this post, I’ll use a directory named JavaDevCentral present inside the /tmp folder which has the below organization and content.

/tmp
├── JavaDevCentral
│   ├── GoogleGuava
│   │   └── guava-cache.txt
│   ├── Java11
│   │   └── optional-notes.txt
│   ├── Java8
│   │   └── stream-notes.txt
│   └── java-notes.txt

Starting Java Simple Web Server from the command line

Once we have Java 18 installed, we can start the Simple Web Server from the command line by running jwebserver. Then it will start serving the directory (and its subdirectories) from which we run it.

The jwebserver is present in the bin folder of the JDK installation. In macOS, it is present in the folder /Library/Java/JavaVirtualMachines/jdk-18.jdk/Contents/Home/bin.

jwebserver

Note: In order to run the jwebserver command, we must have added the Java bin folder path (the above mentioned path) to the PATH environment variable like,

PATH=$PATH:$JAVA_HOME/bin

i.e., assuming JAVA_HOME is set to /Library/Java/JavaVirtualMachines/jdk-18.jdk/Contents/Home (on a macOS).

If not, we must use the full path to the jwebserver.

Okay, getting back… Once the web server has started, it runs in the foreground and will print a message to the console (i.e., the terminal in which we’ve run it).

Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving /private/tmp/JavaDevCentral and subdirectories on 127.0.0.1 port 8000
URL http://127.0.0.1:8000/

It gives us the following information:

  • I’ve run the jwebserver from the /tmp/JavaDevcentral folder in mac. Thus it is serving that folder and all of its subdirectories.
  • It is running on the localhost (127.0.0.1) (a.k.a loopback address) and on port 8000 by default.

Accessing the web server and resource contents

We can access the content that is served by accessing http://127.0.0.1:8000 or http://localhost:8000 in a browser. It will list all the contents of the directory which it is serving.

jwebserver-directory-listing

It shows the files and directories (as links) from the folder which the web server is serving. Now, we can access the files and directories via the browser by clicking on them.

For example, clicking on java-notes.txt will take us to http://127.0.0.1:8000/java-notes.txt (or http://localhost:8000/java-notes.txt) and will show the file contents in the browser.

Clicking on a folder will take us into that folder and will display its contents. Clicking on GoogleGuava folder takes us to http://127.0.0.1:8000/GoogleGuava and shows,

jwebserver-nested-directory-listing

Stopping the web server from command line

The server will keep on running till we stop it. On unix platforms, we can stop the server by running Ctrl + C in the terminal window (which sends a SIGINT signal).

Logging output on the console

Whenever we access some resource (path) on the web server, it logs the request with some information on the console (System.out). When we access the root path (http://localhost:8000), we can see the below message printed,

127.0.0.1 - - [22/Mar/2022:10:36:34 +0530] "GET / HTTP/1.1" 200 -

By default, the logging output level is set to INFO, and it uses the Common Logfile Format. Its format is as follows.

remotehost rfc931 authuser [date] "request" status bytes

It prints the rfc931 (the remote logname of the user), authuser and bytes as -(hyphen).

The rest of the information it captures are:

  • The localhost address in which the server is running.
  • Date and time of the request.
  • The request as it came from the client. This includes:
    • The HTTP verb of the request (GET)
    • The path that was accessed (/ in this case)
    • HTTP version
  • The status code of the response.

If we access a file - say java-notes.txt through http://localhost:8000/java-notes.txt, then it logs,

127.0.0.1 - - [22/Mar/2022:10:38:34 +0530] "GET /java-notes.txt HTTP/1.1" 200 -

Supported HTTP verbs

It only supports HEAD and GET HTTP verbs. Requests with other verbs (POST, PUT, DELETE, CONNECT, OPTIONS, TRACE or PATCH) will return a HTTP 405 (Method Not Allowed). Any other custom verb will return HTTP 501 (Not Implemented).

Let us try to make an OPTIONS call via curl (or you can use any REST API client like Postman or Insomnia as well).

curl -i --request OPTIONS \
  --url http://localhost:8000/
HTTP/1.1 405 Method Not Allowed
Date: <some_date>
Allow: HEAD, GET
Content-length: 0

It returned HTTP 405 Method Not Allowed response. The response has a response header called Allow, which specifies the list of supported headers.

If we use some custom verb, it returns HTTP 501 Not Implemented as shown below.

curl -i --request CUSTOM_VERB \
  --url http://localhost:8000/
HTTP/1.1 501 Not Implemented
Date: <some_date>
Content-length: 0

It doesn’t list or serve symbolic links or hidden files. Let us create a symbolic link named ’link-to-java8-stream-notes’ in /tmp/JavaDevCentral folder pointing to the stream-notes.txt in Java8 folder.

ln -s /tmp/JavaDevCentral/Java8/stream-notes.txt link-to-java8-stream-notes

Now, if we access the root path (/), the symlink link-to-java8-stream-notes won’t show up. We cannot access it directly (http://localhost:8000/link-to-java8-stream-notes) as well. This will return 404 with a “File not found” message.

The same is true for hidden files. If we create a hidden file like,

touch .hidden-file.txt

It doesn’t show up in the web server directory listings and cannot be accessed directly as well (http://localhost:8000/hidden-file.txt will return a HTTP 404).

MIME type

The web server will automatically set the correct MIME type.

When accessing the root folder (or a html file), the Content-Type HTTP response header is set to text/html.

curl -I --request GET \
  --url http://localhost:8000
HTTP/1.1 200 OK
Date: <some_date>
Last-modified: <some_date>
Content-type: text/html; charset=UTF-8
Content-length: 368

If we access a text file, the Content-Type HTTP response header will be text/plain.

 curl -I --request GET \
  --url http://localhost:8000/java-notes.txt
HTTP/1.1 200 OK
Date: <some_date>
Last-modified: <some_date>
Content-type: text/plain
Content-length: 26

Other options available when running the web server

So far, we have explored the Java 18 simple web server utility by using the default options/behaviour. But it also provides a lot of options we can use on the command line when we start the web server. Let us look at them now.

Changing the address

By default, it runs binding to the loopback address or the localhost. We can change this to bind to all interfaces by passing -b option with 0.0.0.0. This will allow the server to listen on all available interfaces.

jwebserver -b 0.0.0.0

This starts the web server which prints,

Serving /private/tmp/JavaDevCentral and subdirectories on 0.0.0.0 (all interfaces) port 8000
URL http://192.168.1.5:8000/

For more context on 0.0.0.0, see,

Now the server will be accessible at http://192.168.1.5:8000. But we can access it via localhost as well (as we have done before).

Changing the port

By default, it runs the server on port 8000. To run the server on a different port, we can pass it via the -p option (or the long option name –port). The below starts the web server on port 8080.

jwebserver -p 8080

Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving /private/tmp/JavaDevCentral and subdirectories on 127.0.0.1 port 8080
URL http://127.0.0.1:8080/

Serving a different directory

By default it will serve the directory from which we run the jwebserver command. If we want to serve a different directory, we can use the -d option (the long name is –directory).

Let’s say we want to host/serve the Java8 folder. We can run the below from any directory. It will start the server which serves the folder which we have passed to the -d option.

jwebserver -d /tmp/JavaDevCentral/Java8

Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving /tmp/JavaDevCentral/Java8 and subdirectories on 127.0.0.1 port 8000
URL http://127.0.0.1:8000/

Accessing http://localhost:8000 now shows the contents of the Java8 folder.

jwebserver-custom-directory-listing

Configuring the output (logging) level

As we have seen earlier, by default, it uses a logging level of INFO. There are two more options which we can use through the -o option name (–output).

No logging

If we don’t want to log anything, then we can pass the log/output level as none.

jwebserver -o none

In this configuration, the server will not log anything to the terminal console when we access the resources.

Verbose logging

We can configure a verbose logging output, which will include the request and response headers and the absolute path of the requested resource.

jwebserver -o verbose

An example is shown below accessing java-notes.txt (not all headers are shown).

127.0.0.1 - - [22/Mar/2022:10:40:26 +0530] "GET /java-notes.txt HTTP/1.1" 200 -
Resource requested: /private/tmp/JavaDevCentral/java-notes.txt
> Accept-encoding: gzip, deflate, br
> Sec-fetch-dest: document
> Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
> Sec-fetch-user: ?1
> Referer: http://localhost:8000/
> Connection: keep-alive
> Sec-fetch-site: same-origin
> Host: localhost:8000
> Accept-language: en-US,en;q=0.9
> ... other headers...
>
< Date: <some_date>
< Last-modified: <some_date>
< Content-type: text/plain
< Content-length: 26
<

Getting help and version

To get help on options, use -h option (–help) which shows all options with descriptions. We can get the version of jwebserver by running -version (or –version)

jwebserver -version
jwebserver 18

Conclusion

This post covered the Java 18 simple web server command line tool. We learnt how to start a web server from the command line and use it to serve static files. We also learnt about the configuration options it has.

Check out the java8 category for Java 8 classes and features.

Referecens