Now that the dust has settled on the launch of Java 8, we can begin to see the benefits that all these new features will bring to those of us willing to throw off the yoke of corporate oppression and start committing lambda expressions to a “Java 5” code base. The possibilities that lambdas bring, along with default methods, and the startling addition of static methods in interfaces are real game changers. For instance, it is now possible to write an entire Java application in a single interface! Those of us who have long railed against the tyranny of a single-class-per-file can now rejoice at being able to place all of our logic in a single file, and it doesn’t even need to be a class. As you will see from this post, the future is here and it is beautiful.

Let’s start with the classic example to get the ball rolling, with our Hello, World interface:

public interface HelloWorld { public static void main(String...args) { System.out.println("Wat"); } }

Crank out your Java 8 compiler and compile and run this baby:

$ java HelloWorld Wat

Wat indeed, my friend. Wat indeed. Java 8 has already blown my tiny mind and it’s not even 9am.

OK, so we can write trivial applications this way. But what about something bigger, better, more web scale. Like a web server. After all, what could possibly be more web scale than a web server? Lets do this.

We will write a basic web server in a single interface. It’ll only handle GETs and text/plain and it will probably fall over for anything out of the ordinary. But hey. Web scale. The interface itself will define an abstract request handler and we will then use lambdas to implement handlers for particular URL paths.

First we’ll start with the basic interface for handling HTTP requests. It will take the path requested and any query parameters as an argument (as a Map from strings to lists of strings) and return a string body as the response:

public interface WebServer { String handle(String path, Map<String, List<String>> query);

So how to do we go about actually implementing the functionality? Obviously, we can use a lambda to implement this functional interface, but we also need some mechanism to dispatch to multiple handlers based on the requested path. There are several ways to do this. An obvious one would be to have some sort of Map that is used to lookup a handler for a particular path. But where should this map live? It cannot be an instance variable, because interfaces cannot have instance variables. We could make it a static field, but we don’t want to resort to global state. A simple (if not very efficient) approach is to have a chain of handlers: each handler checks if the requested path matches itself (and if so handles the request), or otherwise delegates the call to another handler. Let’s start with the base handler that returns a “Not Found” message:

static WebServer create() { return (request, query) -> "Not Found: " + request; }

Calling WebServer.create() will now return a handler that reports that the requested path could not be found. (Obviously this would be a 404 normally, but we are keeping things simple). We can now add the ability to chain a new handler onto an existing one. We can achieve this using a default method:

default WebServer bind(String path, WebServer handler) { return (request, query) -> path.equals(request) ? handler.handle(request, query) : this.handle(request, query); }

Here we return a new WebServer (as a lambda expression) that checks whether the requested path matches the one we have bound a handler to, and (if so) executes that handler, otherwise we delegate to the “this” handler (i.e., the one we called .bind() on). This allows us to build up routing logic by chaining bind() calls in a nice fluent interface.

Now lets start writing the actual logic of the web server: accepting requests, decoding them, and returning a response. For simplicity our server will be entirely single threaded, but it would not be too difficult to use an Executor to fix this (or non-blocking I/O). We also will make no attempt to handle errors, letting them propagate to the caller (and halting the server in the process). Again, this is just for demonstration purposes. To start the main loop of the server, we will add another default method that waits for clients to connect and processes their requests:

default void start(int port) throws IOException { try (ServerSocket server = new ServerSocket(port)) { while (true) { try (Socket client = server.accept()) { // Parse the request line and split into path and query String[] request = request(client).split("\\?", 2); String query = request.length > 1 ? request[1] : ""; String response = handle(request[0], parseQuery(query)); respond(client, response); } } } }

Simple enough. We start a server socket on the given port and then loop accepting connections. For each client connection we read and parse the request, delegate to our handler method, and finally write the response back to the client. The try-with-resources will ensure that each client socket is closed after each request. We need a couple of helper methods to finish the implementation, which we will implement as simple static methods. Firstly, reading the request and sending the response:

static String request(Socket client) throws IOException { BufferedReader reader = new BufferedReader( new InputStreamReader(client.getInputStream(), UTF_8)); return reader.readLine().split("\\s+")[1]; } static void respond(Socket client, String response) throws IOException { try (PrintWriter out = new PrintWriter( new OutputStreamWriter(client.getOutputStream(), UTF_8))) { out.print("HTTP/1.1 200 OK\r

"); out.print("Content-Type: text/plain\r

"); out.print("\r

"); out.println(response); } }

These should be straightforward. The first just reads the first line of the request, which should be of the form “GET /hello?a=b HTTP/1.1”. As before, we make no attempt at robust processing here. The second writes some basic HTTP response headers and then the response string returned from our handler. One nicety in Java 8 is the presence of the java.nio.charset.StandardCharsets class which provides the UTF_8 constant used above (as a static import). The final piece of the puzzle is decoding the query string into a Map. We have already separated the path from the query string by splitting the request on the “?” character in the start() method above. We now need to further split that by “&” characters to get each field, split those on “=” to get the name and (optional) value, and finally we need to URL-decode both parts. If there are multiple values specified for the same parameter then we should combine them into a list, preserving the order in which they appear. Phew! This sounds like a complex procedure, but luckily Java 8 stream processing makes it incredibly simple (with thanks to Chris Clifton for helping me get it working):

static Map<String, List<String>> parseQuery(String queryString) { return asList(queryString.split("&")) .stream() .map(s -> s.split("=", 2)) .collect(groupingBy(a -> decode(a[0])), mapping(p -> decode(p.length > 1 ? p[1] : ""), toList()))); } static String decode(String encoded) { try { return URLDecoder.decode(encoded, "UTF-8"); } catch (UnsupportedEncodingException ex) { throw new RuntimeException(ex); } }

The only ugliness here is the separate decode procedure needed, because (i) URLDecoder.decode takes a charset name rather than the charset and so can throw an UnsupportedEncodingException (checked), and (ii) the new collection methods do not allow their lambdas to throw checked exceptions. Both of these seem like warts to me. But anyway, how does this work? Firstly, we split the query string into its components on the “&” character and convert to a list using Arrays.asList() [statically imported]. We then create a stream from the result and apply a mapping that splits each component on the “=” character to separate the name from the value. Finally, we collect this into a map using a complex chained collector. This is the most complex part of the puzzle. First, we are using the groupingBy collector to collect the results into a Map where each key is given by the parameter name (the first element in the array resulting from our previous split). We then map the values of the Map to the values of the parameters (the second elements from our array), defaulting to an empty string if not present. Finally, we collect together values from duplicate keys into a list using the toList() collector.

I am in two minds about this streams API. On the one hand, it is clearly very powerful. On the other, that snippet took a long time to write. (In large part not helped by using an old version of IntelliJ 13 that insisted that there was a type error: the latest version correctly reports good code). It’s also not clear to me that the result is particularly readable, but I would guess it is as readable as the equivalent written out with explicit loops.

Anyway, we are now ready to finish our web server with the main() method. This creates an empty web server, binds a couple of handlers, and then starts it running:

static void main(String...args) throws IOException { WebServer.create() .bind("/hello", (path, query) -> "Hello, World!") .bind("/echo", (path, query) -> query.toString()) .start(9090); } }

Success! If you compile and run this file (see below for the full source) with Java 8 then you will have a primitive web server running on port 9090. Notice that the compiler only produces a single WebServer.class file! You can try it in your browser or from the terminal using cURL:

$ curl http://localhost:9090 Not Found: / $ curl http://localhost:9090/hello Hello, World! $ curl 'http://localhost:9090/echo?a=1&b=2&c=3&b=4' {a=[1], b=[2, 4], c=[3]}

So what have we learnt? Firstly, that Java 8 provides some powerful constructs for creating functional applications in minimal code. Secondly, that that power can be abused in all sorts of horrible ways!

Over to you – what is the best (or worst) example of a single-interface Java 8 application that you can create? Remember, classes are the past! Interfaces for ever!

The full Java source code:

import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.net.URLDecoder; import java.util.List; import java.util.Map; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.mapping; import static java.util.stream.Collectors.toList; public interface WebServer { String handle(String path, Map<String, List<String>> query); static WebServer create() { return (request, query) -> "Not Found: " + request; } default WebServer bind(String path, WebServer handler) { return (request, query) -> path.equalsIgnoreCase(request) ? handler.handle(request, query) : this.handle(request, query); } default void start(int port) throws IOException { try (ServerSocket server = new ServerSocket(port)) { while (true) { try (Socket client = server.accept()) { String[] request = request(client).split("\\?", 2); String query = request.length > 1 ? request[1] : ""; String response = handle(request[0], parseQuery(query)); respond(client, response); } } } } static String request(Socket client) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), UTF_8)); return reader.readLine().split("\\s+")[1]; } static void respond(Socket client, String response) throws IOException { try (PrintWriter out = new PrintWriter(new OutputStreamWriter(client.getOutputStream(), UTF_8))) { out.print("HTTP/1.1 200 OK\r

"); out.print("Content-Type: text/plain\r

"); out.print("\r

"); out.println(response); } } static Map<String, List<String>> parseQuery(String queryString) { return asList(queryString.split("&")) .stream() .map(s -> s.split("=", 2)) .collect(groupingBy(a -> decode(a[0]), mapping(p -> decode(p.length > 1 ? p[1] : ""), toList()))); } static String decode(String encoded) { try { return URLDecoder.decode(encoded, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } static void main(String...args) throws IOException { WebServer.create() .bind("/hello", (path, query) -> "Hello, World!") .bind("/echo", (path, query) -> query.toString()) .start(9090); } }