Integrating HTML5 routing in a Java web application
In this article I’m going to present a solution for hosting a single page application using HTML5 / client-side routing in a Java web application. This is not easily supported out of the box, so I thought it would be helpful to write a dedicated article that presents a ready-to-use solution.
Traditionally routing is done server-side. The server receives a HTTP request, determines which page of the application is requested and then renders that page itself.
However as browser capabilities increased, frontend logic shifted more and more from the server to the client / browser. With the rise of single page applications (SPA) developers also employed routing on the client-side. The server would just deliver the SPA to the browser, and the SPA would then do the routing to the actual page that should be displayed. Since browser APIs did not allow for changing the URL without reloading the entire page, early client-side routing solutions used a hash symbol (#
) to separate parts of the URL that were routed on the client side. Traditionally the hash was used to navigate to anchor elements (<a>
) in HTML documents and thus were ignored for server-side routing, also changing the URL after the hash did not cause browsers to reload.
As browser capabilities increased further, the HTML5 standard introduced new methods to the history API that allowed to change the URL on the client-side without causing the browser to reload the entire page. This eliminated the need for the hash and allowed usage of a normal looking URL, that is routed both on the server and on the client-side.
But it also made routing on the server and the client a bit more complicated because now we have to tell the server and the client exactly which parts of the URL are relevant to them. This is what this article is about, at least on the server-side in a classic Java web application using servlets.
Backend
First I’ll start with an assumption: That we put all frontend routes under a sub-path like /view
. This makes the URL slightly longer but it also makes things a lot easier. Regardless of what solution you use, you would need to setup a lot of routes if you want frontend routes, backend routes, and static files to be resolved from the same base path. It's easier if we put each category in it's own subpath like this:
/api
- REST or SOAP or whatever API you are using/public
- Static frontend files/view
- Frontend routes
Second, I’m going to prescribe the use of the UrlRewriteFilter
library for forwarding and redirecting requests. While you can do forwarding with servlet mappings in the web.xml
, there is no way to do redirects without writing code. Plus the UrlRewriteFilter
makes configuration a lot simpler.
So let’s get started. Either you already have an existing Java web application you can create one by using the following command, which creates an empty web app from a Maven archetype:
To include the UrlRewriteFilter library we add a Maven dependency to the pom.xml
:
And add the filter to our web.xml
, preferably after all other filters:
The UrlRewriteFilter uses its own configuration file, which we create under src/main/webapp/WEB-INF/urlrewrite.xml
:
Now we can define the actual routes. I’ll first write down naturally what we are trying to accomplish here:
/view/*
should forward to/index.html
, which we are going to use as the index file for our frontend application/
should redirect to/view
, so users accessing the application are shown the UI/index.html
should redirect to/view
, because the frontend needs to have one constant base URL after which it does its own routing. So if it expects/view
as base URL it wouldn't work if the URL containsindex.html
.
For this we add the following rules to our urlrewrite.xml
:
The rules should be mostly self-explanatory, some notes:
- I changed the order so that the redirects are above the forwarding. Ordering matters in this configuration and otherwise we would get infinite redirects.
from
is a regular expression that matches the URL that we want to intercept.to
is the path to which we want to forward or redirect. Usetype="redirect"
for redirects, otherwiseforward
is the default value.note
elements allow for a simple documentation directly in the config file.- For more detailed information you can check out the manual.
But that’s basically it for the backend.
Frontend
Frontend routing
As this article is focussing on the Java part of routing, I’m not going to write a complete frontend application here. That would involve picking a frontend / routing library and might be more confusing if you are using a different library.
Instead I’m going to use a simple HTML file, that just prints the current frontend route. To create that, just add a index.html
file to the webapp
folder and copy the following contents:
The important thing to note is that the frontend needs to know the base URL that it is running under. This is necessary so that the frontend knows which parts of the URL were already routed by the server and which parts it still needs to route itself. I’ll repeat the last image from the introduction here:
In the example HTML I’m just using the base URL to calculate the current route myself. In a React, Angular, Vue, etc… SPA you would configure the base URL in your applications’ router. For example in React, using the react-router-dom
package, you would configure the BrowserRouter
like so:
<BrowserRouter basename={baseUrl}>
...
</BrowserRouter>
The same is possible in other frameworks and router libraries.
Base URL and application context
Also note that currently the base URL is hard-coded to /view
. This means that the frontend must be routed to /view
, which we did in the filter configuration. But it also means that our Java application needs to run under the root context (/
) of our application server, like https://example.com/
or http://localhost:8080/
. If we wanted to run our application under a different context like https://example.com/app/
or http://localhost:8080/app/
, we would need to change the base URL to /app/view
. For this we could just change the constant, however we will also look into building the base URL dynamically in a later section.
Assets
Another thing to look out for is loading assets (Javascript, CSS, images, …) from the HTML file. Since everything below /view
gets forwarded to the same HTML file we can not use relative paths anymore. Consider that we have a folder public
where we store all static files and want to include a CSS file from there. Usually we would write:
<link rel="stylesheet" href="public/styles.css">
However because the page was loaded from http://example.com/view
or http://example.com/view/dashboard
this would be resolved to:http://example.com/view/public/styles.css
or http://example.com/view/dashboard/public/styles.css
.
Instead we need to use an absolute URL (from the server root) like so:
<link rel="stylesheet" href="/public/styles.css">
Notice the additional slash (/
) in front of the URL. That would always be resolved to http://example.com/public/styles.css
regardless of the page location. As with the base URL, this can be hard-coded against a specific application context or you could create the URL dynamically.
Testing
You can test the example app in any application server that you like. Just build the .war file using mvn package
and deploy it to your server. However as explained above, you must make sure that the application runs under the root context of the server (/
), otherwise the frontend will not be able to determine the current route.
Personally I tested with Wildfly and Tomcat. For those servers you can just rename the .war file to ROOT.war
which these servers will interpret to deploy the application in the context root. You can do this easily in Maven, by changing the finalName
property in the pom.xml
:
When you have the application running, you can access the app with a URL like http://localhost:8080/view/dashboard and the browser should get routed to the frontend and display /dashboard
as the current route.
Congrats, you’re done! 😊
You can check out the resulting code in this repository.
✨ Bonus: Dynamic base URL
As discussed before, we hard-coded the base URL in our frontend and possibly hard-coded some more URLs by including static asset files. Now hard-coding can be an acceptable solution if you are sure that your application will always run under the same context. However if you can anticipate that the application may be deployed under different contexts (for examplehttp://example.com/test
, http://example.com/stage
, http://example.com/live
) then you need to expand your solution. Basically I can see the following options (there are probably more):
- Create multiple builds, for example with multiple index.html files and Maven profiles
- Create the URLs dynamically
In this section I’m going to show a solution for the last option. You don’t necessarily need to follow along, the code is also available in the following branch of the repository.
First we add a utility class for generating our application URLs:
The class has methods for generating URLs for frontend routes, asset files or generic routes. It basically determines the application context at runtime and prepends that to the URL that we give it.
Next we need a mechanism to change the URLs in the HTML dynamically before sending it to the browser. For that we just use a JSP file. So first step is to rename the index.html
to index.jsp
and then change all references to the file in our urlrewrite.xml
filter config.
Then we modify the index.jsp
to use our Route
utility:
Instead of writing the base URL directly into the script we put it into the meta
tag and then read it from the tag in the script. This makes it easier if we want to read the base URL from a separate Javascript file - which is usually the case in a single page application. We also add some static assets (app.js
and styles.css
) to demonstrate how we would use the utility for loading asset files.
And that’s basically it. Now we can deploy the application under any context and the base URL in the frontend adapts dynamically. Again, the results are available in this branch.
Summary
In conclusion we learned about different routing techniques between backend and frontend. We integrated HTML5 routing in a Java web application by forwarding all requests below a sub-path to the same index file, so that the frontend can handle the routing. We wrote a simple frontend that uses a base URL to determine its current route. We learned about some other pitfalls concerning HTML5 routing like hard-coded paths in regards to application context and loading asset files in the frontend. As bonus we implemented a more flexible solution that avoids hard-coded paths in the frontend by creating them dynamically.
Thanks for reading!