Over a million developers have joined DZone.

Java EE 8 MVC + Leaflet Map Demo

DZone's Guide to

Java EE 8 MVC + Leaflet Map Demo

Replace static maps with Java EE 8's new MVC features and Leaflet, an open source JavaScript library for interactive maps.

· Java Zone
Free Resource

What every Java engineer should know about microservices: Reactive Microservices Architecture.  Brought to you in partnership with Lightbend.

This article shows how to create a geographic map using Java EE 8 MVC and Leafleft. My goal is to replace this static boring map with something more sexy. I used the new Java EE MVC and Leaflet for this stuff.

Java EE's new action-based MVC framework will be available in Java EE 8. You can get basic information about Ozark (MVC reference implementation) from this article. Leaflet is an open-source JavaScript library for interactive maps.

For application development, I use Glassfish 4.1.1 from Java EE SDK update 2. For running the app in production, I used Tomcat 7.


Application is divided (as MVC design pattern requires) to three layers:

  • Controller — it gets requests from a web browser, obtains air quality data, and fills it into Model.

  • Model is the container for our data. It's able to retrieve data as a Geo JSON string.

  • View is a standard JSP page. It gets data from Model and renders output to the browser.


Java EE MVC is based on JAX-RS. So creating an application is simple using annotation:

public class GettingStartedApplication extends Application 


Data about air quality (shortly AQ) that I want to display on map is in this JSON file, updated every hour. You can use an online JSON viewer to discover its structure. There is a legend (air quality index) and information about measuring stations and air quality index stated here.

The role of Controller is to read and parse this JSON and populate required information to MapModel that is injected by CDI.

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;

import javax.inject.Inject;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.mvc.annotation.Controller;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

 * The MVC Controller. It waits for HTTP GET requests on his Path: 
 * <code>http://host:port/AppName/@ApplicationPath/@Path</code>
 * @author lvanek
public class MapController 
     * The MVC model
    MapModel mapModel;

     * Method responds to HTTP GET requests
     * @return View path
    public String map()

    return "/WEB-INF/jsp/mapView.jsp";

     * Read JSON with Air quality data and populate MVC model
     * Use:
     * <code>http://jsonviewer.stack.hu/#http://portal.chmi.cz/files/portal/docs/uoco/web_generator/aqindex_eng.json</code><p>
     * for JSON browsing
    private void createModel()
    URL vvv = new URL("http://vvv.chmi.cz/uoco/aqindex_eng.json"); 
    URLConnection urlConnection = vvv.openConnection();

    try (final BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), StandardCharsets.UTF_8.displayName()))) 
    JsonReader jsonReader = Json.createReader(reader);
    final JsonObject root = (JsonObject) jsonReader.read();


    final JsonArray jsonLegend = root.getJsonArray("Legend");

    for (int i = 0; i < jsonLegend.size(); i++)
    JsonObject jsonAqIndex = jsonLegend.getJsonObject(i);

    mapModel.putAirQualiryLegendItem(jsonAqIndex.getInt("Ix"), new AirQualiryLegendItem(jsonAqIndex.getInt("Ix"), 

    final JsonArray jsonStates = root.getJsonArray("States");

    for (int s = 0; s < jsonStates.size(); s++)
    final JsonObject jsonState = jsonStates.getJsonObject(s);
    final JsonArray jsonRegions = jsonState.getJsonArray("Regions");

    for (int i = 0; i < jsonRegions.size(); i++)
    final JsonObject jsonRegion = jsonRegions.getJsonObject(i);

    final JsonArray jsonStations = jsonRegion.getJsonArray("Stations");
    for (int j = 0; j < jsonStations.size(); j++)
    final JsonObject jsonStation = jsonStations.getJsonObject(j);

    if ((jsonStation.containsKey("Lat")) && (jsonStation.containsKey("Lon"))) // 'Prague center' and 'Prague periphery' not have position and components list
    String wgs84Latitude = jsonStation.getString("Lat");
    String wgs84Longitude = jsonStation.getString("Lon");

    mapModel.addAirQualityMeasuringStation(new AirQualityMeasuringStation(jsonStation.getString("Code"),
    catch (Exception e) 

     * Substitute an empty String, when a null value is encountered.
     * @param source String
     * @return Original string, or empty string, if parameter is null
    static String nvl(String source)
    return (source == null) ? "" : source;

Last line of method map() redirects processing to our View.


Model is represented by three classes:Air quality measuring station

  • AirQualityLegendItem represents one Legend item. It contains an AQ index value, its colors, and text description.

  • AirQualityMeasuringStation represents an Air Quality measuring station, and contains unique code, name, WGS-84 position, and AQ index value.

  • MapModel — the model itself. It contains a list of stations and legend.

public class AirQualityLegendItem 
private final int index;
private final String color;
private final String colorText;
private final String description;

 * Constructor
 * @param index Air quality index value
 * @param color Background color
 * @param colorText Text color
 * @param description Index description
public AirQualityLegendItem(int index, String color, String colorText, String description)
this.index = index;
this.color = color;
this.colorText = colorText;
this.description = description;

// getters deleted for shorting

public class AirQualityMeasuringStation
private final String code;
private final String name;
private final String owner;
private final String classification;
private final float wgs84Latitude;
private final float wgs84Longitude;
private final int index;

 * Constructor
 * @param code Station unique code
 * @param name Station name
 * @param owner Station owner
 * @param classification Station classification
 * @param wgs84Latitude WGS-84 latitude
 * @param wgs84Longitude WGS-84 longitude
 * @param index Air quality index value
public AirQualityMeasuringStation(String code, String name, String owner, String classification, float wgs84Latitude, float wgs84Longitude, int index) 
this.code = code;
this.name = name;
this.owner = owner;
this.classification = classification;
this.wgs84Latitude = wgs84Latitude;
this.wgs84Longitude = wgs84Longitude;
this.index = index;

  // getters deleted for shorting

The map model class is anotated as @RequestScoped, and has a name for CDI.

 * The MVC model. Contains data for view:<p/>
 * <code>/WebContent/WEB-INF/jsp/mapView.jsp</code>
 * @author lvanek
public class MapModel 
 *  Air quality indexes, key is index value 
private Map<Integer, AirQualityLegendItem> legend = new HashMap<>(8);

 * Air quality measuring stations
private List<AirQualityMeasuringStation> stations = new ArrayList<>();

 * Date of Air quality index actualization
private String actualized;

public String getActualized() {
return actualized;

public void setActualized(String actualized) {
this.actualized = actualized;

private static final JsonBuilderFactory bf = Json.createBuilderFactory(null);

public Map<Integer, AirQualityLegendItem> getLegend() {
return legend;

public void putAirQualiryLegendItem(int index, AirQualityLegendItem item)
legend.put(index, item);

public AirQualityLegendItem getAirQualiryLegendItem(int index)
return legend.get(index);

public void addAirQualityMeasuringStation(AirQualityMeasuringStation station)

 * Create Geo JSON with Air quality data. It's requested in mapView.jsp by:<p/>
 * <code>var geojson = &lt;c:out value="${mapModel.geoJson}" escapeXml="false"/&gt; ;</code>
 * @return Geo JSON string
public String getGeoJson()
JsonObjectBuilder featureCollection = bf.createObjectBuilder().add("type", "FeatureCollection");

JsonArrayBuilder features = Json.createArrayBuilder();


featureCollection.add("features", features);

JsonObject geoJson = featureCollection.build();

return geoJson.toString();

 * Populate given GEO JSON features list with Air quality stations data
 * @param features GEO JSON features
private void createFeatures(JsonArrayBuilder features)
 * Sort stations by Air quality index to have worst ones on top layer
Comparator<AirQualityMeasuringStation> comparator = new Comparator<AirQualityMeasuringStation>()
public int compare(AirQualityMeasuringStation a1, AirQualityMeasuringStation a2) 
return Integer.compare(a1.getIndex(), a2.getIndex());

Collections.sort(stations, comparator);

 * Traverse stations and create Geo JSON Features
for(AirQualityMeasuringStation station : stations)
String title = station.getCode() + " - " + station.getName();

AirQualityLegendItem legendItem = getAirQualiryLegendItem(station.getIndex());

"#" + legendItem.getColorText(),
"#" + legendItem.getColor(), 

 * Create Geo JSON feature
 * @param title Title for popup
 * @param color Marker text color
 * @param fillColor Marker background color
 * @param wgs84Latitude WGS-84 Latitude
 * @param wgs84Longitude WGS-84 Longitude
 * @param classification Locality classification
 * @param description Air quality index description
 * @return Geo JSON feature
private JsonObjectBuilder createFeature(String title, String color, String fillColor, float wgs84Latitude, float wgs84Longitude, String classification, String description)
JsonObjectBuilder feature = Json.createObjectBuilder()
    .add("type", "Feature");

// Feature properties
JsonObjectBuilder properties = Json.createObjectBuilder()
         .add("popupContent", title)
         .add("description", description)
         .add("classification", classification)
         .add("style", Json.createObjectBuilder()
         .add("weight", 1) 
         .add("color", color)
         .add("fillColor", fillColor)
         .add("fillOpacity", 0.8f)
         .add("opacity", 1));

feature.add("properties", properties);

// Feature geometry
JsonObjectBuilder geometry = Json.createObjectBuilder()
         .add("type", "Point")
         .add("coordinates", Json.createArrayBuilder().add(wgs84Longitude).add(wgs84Latitude));

feature.add("geometry", geometry);

return feature;

Crucial method getGeoJson() will be called from View, and it returns GEO JSON as String. For each station, a Feature is created with this structure:

      "popupContent":"LFRTA - Frydlant",
      "description":"Index on this station is not determined",


Finally, mapView.jsp is rendered. This is not all of its content, but only the crucial parts.

In head section, Leafleft JavaScript and CSS are included:

<%@page contentType="text/html" pageEncoding="UTF-8" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <link href="./../resources/css/styles.css" rel="stylesheet" type="text/css" />
        <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.5/leaflet.css" />
        <script src="http://cdn.leafletjs.com/leaflet-0.7.5/leaflet.js"></script> 
        <title>Java EE 8 MVC + Leaflet Map demo</title>

Then, we need insert map div into page:

 <div id="map" style="height: 800px; position: relative; padding: 0px; margin: 0 auto 0 auto;"></div>

Last but not least, we need some JavaScript code to initialize the map. The crucial part is this line:

var geojson = <c:out value="${mapModel.geoJson}" escapeXml="false"/>;

...which is how MapModel method getGeoJson() called. Then the GEO JSON is processed. For each Feature created, Points are transfered to Circles with appropriate colors.

var map = L.map('map').setView([50.0, 15.5], 8);

L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiZmx5aW5nYmlyZCIsImEiOiJjaWd1dXoycWIwYzZ6dmttNWhvdDJlaG5jIn0.I__xI-EzhnkmRI2BB-1SJg', {
maxZoom: 18,
minZoom: 7,
attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
'<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' +
'Imagery © <a href="http://mapbox.com">Mapbox</a>',
 id: 'xxx', // Use your own Mapbox id !
 accessToken: 'xxx' // Use your own Mapbox accessToken !

var geojson = <c:out value="${mapModel.geoJson}" escapeXml="false"/>;

function onEachFeature(feature, layer) 
var popupContent = "<div style='background-color: " + feature.properties.style.fillColor + ";  padding-left: 2px; padding-right: 2px;'><p style='color: " + feature.properties.style.color + ";'>";

if (feature.properties && feature.properties.popupContent) {
popupContent += "<b> " + feature.properties.popupContent + "</b><br/>";

popupContent += "Classification: " + feature.properties.classification + 
"<br/>Air quality: <b>" + feature.properties.description + "</b></p></div>";


L.geoJson(geojson, {

style: function (feature) {
return feature.properties && feature.properties.style;

onEachFeature: onEachFeature,

pointToLayer: function (feature, latlng) 
return L.circleMarker(latlng, {
radius: 8,
fillColor: feature.properties.fillColor,
color: feature.properties.color, 
weight: feature.properties.weight,
opacity: feature.properties.opacity,
fillOpacity: feature.properties.fillOpacity



Creating a simple application with upcoming Java MVC frameworks is very straightforward. I was very pleasantly surprised by the speed of development.

Running application

Running on Glassfish

Glassfish 4.1.1 contains all required technologies, instead of Ozark. I use Eclipse tooling for development (not Maven), so I only put into /WebContent/WEB-INF/lib files ozark-1.0.0-m02.jar and javax.mvc-api-1.0-edr2.jar.

You can find the full source code of the Eclipse project for Glassfish here.

Running on Apache Tomcat 7Libraries on Tomcat

Here, the situation is much more difficult. We need to download CDI implementation WELD (weld-servlet.jar), JAX-RS API Jersey, and incorporate into an application with jstl-1.2_1.jar and javax.json.jar as in the above image.

jersey-cdi*.jar's are 'borrowed' from Glassfish 4.1.1.

Get CDI to work

Create WebContent/META-INF/context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Context reloadable="true">

    <!-- Default set of monitored resources -->

   <Resource name="BeanManager"

Get Jersey to work

On web.xml, add this content:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 


<servlet-name>Jersey REST Service</servlet-name>

<!-- Register JAX-RS Application, if needed. -->

<!-- Register resources and providers -->

<!-- Enable Tracing support. -->


<servlet-name>Jersey REST Service</servlet-name>

      Needed to add CDI to Tomcat via Weld 2. It assumes that you have added weld-servlet.jar to WEB-INF/lib and that you
      edited META-INF/context.xml (or the default context.xml) and added a Resource entry for CDI. 

      See http://jsf2.com/using-cdi-and-jsf-2.2-faces-flow-in-tomcat/


On Tomcat, application path is: http://host:port/OzarkMapApp/rest/map

Microservices for Java, explained. Revitalize your legacy systems (and your career) with Reactive Microservices Architecture, a free O'Reilly book. Brought to you in partnership with Lightbend.

java ee ,java ee 8 ,java ,mvc

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}