Java doesn't have the best reputation when it comes to web development. Let's face it, it's a verbose language littered with large frameworks (they have their place, but not all tasks require a framework) that can be quite daunting to learn. It would be tough to argue against using a dynamic language such as Ruby or Javascript when deciding to build a web application. They have great tooling and dynamic languages tend to speed up web development productivity.

How large is the gap though? If an experienced Java dev wants to build a web site / application should they learn a new language or possibly have a single page app with a Java backed REST service? Or should they pick one of the many frameworks GWT, Vaadin, JSF, PrimeFaces, one of many MVC frameworks? After exploring both single page application frameworks and Java frameworks there seems to be quite a large learning curve, one that there isn't time for. Why can't we build a very quick server side rendered website for a quick MVP or even a full size project?

Building a server side rendered website in javascript is pretty straightforward and has minimal barrier to entry. Pick up node.js, express and a HTML templating engine (handlebars, mustache, jade, ...). How close can we get to this with Java?

Lightweight Java HTTP Server

Undertow is a great lower level web server and we can compare it to node.js in our example. Undertow has been covered fairly extensively on this site so we will jump right into the code. For a refresher or intro to Undertow take a look at Embedded Java HTTP Server.

HTTP Handlers

We will be creating four routes for this example. A simple static homepage, a hello {name} page which uses a query parameter, a not found 404 page and finally a default 500 internal server error page. These should all be fairly straight forward.

// Simple not found 404 page
public static void notFound(HttpServerExchange exchange) {
    exchange.setStatusCode(404);
    Exchange.body().sendHtmlTemplate(exchange, "static/templates/src/notfound", SimpleResponse.create());
}

// Default error page when something unexpected happens
public static void error(HttpServerExchange exchange) {
    exchange.setStatusCode(500);
    Exchange.body().sendHtmlTemplate(exchange, "static/templates/src/serverError", SimpleResponse.create());
}

// Render homepage
public static void home(HttpServerExchange exchange) {
    exception(exchange);
    Exchange.body().sendHtmlTemplate(exchange, "static/templates/src/home", SimpleResponse.create());
}

// Render hello {name} page based on the name query param.
public static void hello(HttpServerExchange exchange) {
    exception(exchange);
    String name = Exchange.queryParams()
                          .queryParam(exchange, "name")
                          .filter(s -> !Strings.isNullOrEmpty(s))
                          .orElse("world");
    SimpleResponse response = SimpleResponse.create()
                                            .with("name", name);
    Exchange.body().sendHtmlTemplate(exchange, "static/templates/src/hello", response);
}

// Helper function to forcibly throw an exception whenever the query
// parameter exception=true
private static void exception(HttpServerExchange exchange) {
    if (Exchange.queryParams().queryParamAsBoolean(exchange, "exception").orElse(false)) {
        throw new RuntimeException("Poorly Named Exception!!!");
    }
}

Middleware and Routes

Now that we have our HttpHandlers we need to tie them to specific routes using our RoutingHandler. While we are at it let's add some useful middleware such as logging, metrics, exception handling, and gzip.

// We are currently handling all exceptions the same way
private static HttpHandler exceptionHandler(HttpHandler next) {
    return CustomHandlers.exception(next)
       .addExceptionHandler(Throwable.class, WebpackServer::error);
}

// Useful middleware
private static HttpHandler wrapWithMiddleware(HttpHandler handler) {
    return MiddlewareBuilder.begin(BlockingHandler::new)
                            .next(CustomHandlers::gzip)
                            .next(ex -> CustomHandlers.accessLog(ex, log))
                            .next(CustomHandlers::statusCodeMetrics)
                            .next(WebpackServer::exceptionHandler)
                            .complete(handler);
}

// Simple routing, anything not matching a route will fall back
// to the not found handler.
private static final HttpHandler ROUTES = new RoutingHandler()
    .get("/", timed("home", WebpackServer::home))
    .get("/hello", timed("hello", WebpackServer::hello))
    .get("/static*", timed("static", CustomHandlers.resource("", (int)TimeUnit.HOURS.toSeconds(4))))
    .setFallbackHandler(timed("notfound", WebpackServer::notFound))
;

public static void main(String[] args) {
    SimpleServer server = SimpleServer.simpleServer(wrapWithMiddleware(ROUTES));
    server.start();
}

HTML Templating

Handlebars is widely used and simple to learn let's start with that. We can create a layout to share our common page formatting as well as many re-useable widgets. We will only list some files here but all of the front end of StubbornJava can be found here.

Simple page layout.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
    <meta http-equiv="x-ua-compatible" content="ie=edge"/>
    <meta name="description" content="{{#if metaDesc }}{{metaDesc}}{{/if}}"/>
    <link rel="stylesheet" href="/static/css/app.css"/>

    <title>{{> title}}</title>
  </head>

  <body>
    {{> static/templates/src/nav}}
    {{> content}}

    {{> static/templates/src/footer}}
    <script type="text/javascript" src="/static/js/app.js"></script>
  </body>
</html>

Dynamic hello page using the base layout.

{{#> static/templates/src/layout}}
  {{#*inline "title"}}Hello {{name}}!{{/inline}}
  {{#*inline "content"}}
    <div class="page-heading">
      <div class="container center-text">
        <h1>Hello {{name}}!</h1>
        <p>Edit the name by modifying the <code>?name=</code> query parameter. Empty string or a non existent parameter defaults to world.</p>
      </div>
    </div>
  {{/inline}}
{{/ static/templates/src/common/_base-layout}}

Handling Assets

Now that we have a web server that can handle requests how do we manage all of our assets? We need CSS (sass / less), images, javascript libraries. Luckily javascript already has some pretty good tools for the job.

npm

We can use npm to handle all of our javascript dependencies and most of our css ones as well. It doesn't do anything fancy other than put files in the node_modules directory. Disclaimer: This is mostly copy pasted feel free to open a PR if something can be improved.

{
  "name": "StubbornJava-examples",
  "description": "StubbornJava-examples",
  "author": "",
  "dependencies": {
    "anchor-js": "^3.2.1",
    "bootstrap": "^4.0.0-alpha.6",
    "font-awesome": "^4.5.0",
    "jquery": "^3.1.0"
  },
  "devDependencies": {
    "babel": "^6.23.0",
    "babel-core": "^6.23.1",
    "babel-loader": "^6.3.2",
    "babel-preset-es2015": "^6.22.0",
    "copy-webpack-plugin": "^4.0.1",
    "css-loader": "^0.26.1",
    "exports-loader": "^0.6.3",
    "extract-text-webpack-plugin": "^2.0.0-rc.3",
    "file-loader": "^0.10.0",
    "imports-loader": "^0.7.0",
    "node-sass": "^4.5.0",
    "optimize-css-assets-webpack-plugin": "^1.3.0",
    "raw-loader": "^0.5.1",
    "resolve-url-loader": "^2.0.0",
    "sass-loader": "^6.0.1",
    "style-loader": "^0.13.1",
    "url-loader": "^0.5.7",
    "webpack": "^2.2.1",
    "webpack-config": "^7.0.0"
  },
  "scripts": {}
}

Webpack

Now that we have many of our assets we don't want to go back in time too far and have our webpage make 40+ requests just to load javascript and css. Webpack exists, it's cool, let's try that out. With Webpack we can now minify / uglify our js / css and also control how many output files we have (Plus many many more features). Disclaimer: This is mostly copy pasted feel free to open a PR if something can be improved.

var ExtractTextPlugin = require('extract-text-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');
var OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');

var webpack = require('webpack');
var path = require('path');

module.exports = {
    // The standard entry point and output config
    entry: {
        app: './src/app'
        // Use extra files to induclde dependencies only used on some pages
        // A good exmaple would be a graphing library.
        //extras: './src/extras'
    },
    output: {
        path: __dirname + '/assets/static',
        filename: 'js/[name].js',
        chunkFilename: '[id].js',
        publicPath: '/assets/'
    },
    module: {
        loaders: [
            {
                test: /\.(otf|eot|ttf|woff)/,
                loader: 'file-loader?name=fonts/[name]-[hash].[ext]'
            }, {
                test: /\.(png|jpg|gif|ico|svg)/,
                loader: 'file-loader?name=images/[name].[ext]'
            }, {
                test: /\.css$/,
                loader: ExtractTextPlugin.extract({
                                            fallback: "style-loader",
                                            use: "css-loader",
                                          })
            }, {
                test: /\.scss$/,
                loader: ExtractTextPlugin.extract({
                                            fallback: "style-loader",
                                            use: "css-loader!sass-loader",
                                          })
            }, {
                test: /\.json$/,
                loader: 'json-loader'
            }, {
                test:    /\.js$/,
                // Some of the 3rd party libs are in ES6 so we need to run babel
                //exclude: /(node_modules|bower_components)\/[^s]/, //^shared
                loader:  'babel-loader',
                query:   {
                    presets: ['es2015']
                }
            }
        ]
    },

    // Use the plugin to specify the resulting filename (and add needed behavior to the compiler)
    plugins: [
        new ExtractTextPlugin('css/[name].css'),
        new webpack.optimize.UglifyJsPlugin({minimize: true}),
        new OptimizeCssAssetsPlugin(),
        new CopyWebpackPlugin([
            {
                from: 'src/**/*.hbs',
                to: './templates/'
            }
        ]),
        new webpack.ProvidePlugin({
          // We shouldn't need all of these but ran into some issues
          // Adding all of them fixed it :(
          $: "jquery",
          jQuery: "jquery",
          'window.$': 'jquery',
          'window.jQuery': 'jquery'
       })
    ],

    devtool: '#sourcemap',

    resolveLoader: {
        modules: ["node_modules"]
    },

    resolve: {
        extensions: ['.js', '.json', '.hbs', '.jpg', '.png', '.less', '.css'],
        modules: [path.resolve(__dirname, "src"), "node_modules"]
    }
};

App.js

The app.js file is what pulls everything together. You can have more than one entrypoint file in Webpack but we are sticking to one for now.

import $ from 'jquery';

// Bootstrap
import 'bootstrap/scss/bootstrap.scss';

import './app.scss';

// Add all the js files you need.
import 'bootstrap/js/src/dropdown.js';

Workflow

How exactly do Webpack and npm hook into Java now? Simple, it's just input and output files. In our Webpack config we are outputting everything into an assets directory. All we need to do is add this directory to our class path and Java can now read the files. While developing simply run webpack --progress --watch and you can quickly develop locally. Sometimes you need to restart when adding a brand new hbs template open a PR if you know how to fix that please. When building an executable jar you just need to remember to run webpack before you build the Jar and you are good to go.

Demo Site

You should be able to clone the repo and run the WebpackServer example. As long as you run webpack --progress --watch everything should work locally. If there are issues loading assets try changing the assets.root property found here. This is a hack to make files load faster in the IDE, for some reason even with native hooks turned on the IDE's are slow to pick up file changes. Here are screen shots of the four pages. If you are not a strong HTML / CSS developer consider using a site template to make your website stand out.

home





hello {name}





Not Found





Server Error