Introduction
This is a simple Spring MVC
application with Java configuration
instead of XML
.
Our application won’t require an application server to deploy to. We are going to use Embdded Tomcat
. Also, the application will be packaged to singler executable JAR, including dependencies. We call this Fat JAR
or Uber JAR
. It is similar to Spring Boot. To achieve it, we’ll use Apache Maven Shade Plugin
.
We’ll also use JSP
as our template engine.
For this project, we create project foler SpringMVCGreetingsApp
.
Requirements
Java 11
- OpenJDK, GraalVM, Zulu Builds of OpenJDK, Amazon Corretto, SapMachine, Liberica JDKMaven
- you must have installed Maven or create Maven wrapper.IDE
- Intellij IDEA, NetBeans, Eclipse
Project Structure
SpringMVCGreetingsApp
└── src
└── main
└── java
└── com
└── julianjupiter
└── springmvcgreetings
└── configuration
└── controller
└── server
└── resources
└── webapp
└── assets
└── css
└── js
└── WEB-INF
└── views
└── greetings
└── home
└── includes
└── pom.xml
Maven Dependencies
Copy and paste the following to your pom.xml
:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.julianjupiter</groupId>
<artifactId>SpringMVCGreetingsApp</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringMVCGreetingsApp</name>
<description>Spring MVC Application with Java configuration.</description>
<properties>
<failOnMissingWebXml>false</failOnMissingWebXml>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<tomcat.version>9.0.37</tomcat.version>
<spring.version>5.2.8.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>jakarta.servlet.jsp</groupId>
<artifactId>jakarta.servlet.jsp-api</artifactId>
<version>2.3.6</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/webapp</directory>
<targetPath>META-INF/resources</targetPath>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.julianjupiter.springmvcgreetings.SpringMVCGreetingsApplication</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Codes
Add Server
Since we’re using Embedded Tomcat, we’ll create our own server programmatically.
Server.java
package com.julianjupiter.springmvcgreetings.server;
public interface Server {
void run(String[] args);
static Server newServer() {
return new TomcatServer();
}
}
TomcatServer.java
package com.julianjupiter.springmvcgreetings.server;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.webresources.StandardRoot;
import java.io.File;
import java.lang.System.Logger;
class TomcatServer implements Server {
private static final Logger LOGGER = System.getLogger(TomcatServer.class.getName());
private static final String DEFAULT_HOST = "localhost";
private static final int DEFAULT_PORT = 8080;
private static final String DEFAULT_CONTEXT_PATH = "/";
private static final String DOC_BASE = ".";
private static final String ADDITION_WEB_INF_CLASSES = "src/main/";
private static final String WEB_APP_MOUNT = "/WEB-INF/classes";
private static final String INTERNAL_PATH = "/";
@Override
public void run(String[] args) {
int port = this.port(args);
Tomcat tomcat = this.tomcat(port);
try {
tomcat.start();
} catch (LifecycleException exception) {
LOGGER.log(Logger.Level.ERROR, exception.getMessage());
LOGGER.log(Logger.Level.ERROR, "Exit...");
System.exit(1);
}
LOGGER.log(Logger.Level.INFO, "Application started with URL {}.", DEFAULT_HOST + ":" + port + DEFAULT_CONTEXT_PATH);
LOGGER.log(Logger.Level.INFO,"Hit Ctrl+D or Ctrl+C to stop it...");
tomcat.getServer().await();
}
private int port(String[] args) {
if (args.length > 0) {
String port = args[0];
try {
return Integer.valueOf(port);
} catch (NumberFormatException exception) {
LOGGER.log(Logger.Level.ERROR, "Invalid port number argument {}", port, exception);
}
}
return DEFAULT_PORT;
}
private Tomcat tomcat(int port) {
Tomcat tomcat = new Tomcat();
tomcat.setHostname(DEFAULT_HOST);
tomcat.getHost().setAppBase(DOC_BASE);
tomcat.setPort(port);
tomcat.getConnector();
this.context(tomcat);
return tomcat;
}
private Context context(Tomcat tomcat) {
Context context = tomcat.addWebapp("", DOC_BASE);
File classes = new File(ADDITION_WEB_INF_CLASSES);
String base = classes.getAbsolutePath();
WebResourceRoot resources = new StandardRoot(context);
context.setResources(resources);
return context;
}
}
Add Launcher
SpringMVCGreetingsApplication.java
package com.julianjupiter.springmvcgreetings;
import com.julianjupiter.springmvcgreetings.server.Server;
public class SpringMVCGreetingsApplication {
public static void main(String[] args) {
Server.newServer().run(args);
}
}
Add Configuration
We’ll create our configuration using Java.
ApplicationWebConfiguration.java
package com.julianjupiter.springmvcgreetings.configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.JstlView;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.julianjupiter.springmvcgreetings")
public class ApplicationWebConfiguration implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/assets/css/**").addResourceLocations("/assets/css/");
registry.addResourceHandler("/assets/js/**").addResourceLocations("/assets/js/");
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/views/", ".jsp")
.viewClass(JstlView.class);
}
}
ApplicationInitializer.java
package com.julianjupiter.springmvcgreetings.configuration;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class ApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{ApplicationWebConfiguration.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
Add Controller
We’ll create only two (2) controllers - for index/home and greetings.
HomeController.java
package com.julianjupiter.springmvcgreetings.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
import java.lang.System.Logger;
@Controller
public class HomeController {
private static final Logger LOGGER = System.getLogger(GreetingsController.class.getName());
@RequestMapping(value = {"/", "/home"}, method = RequestMethod.GET)
public String index(HttpServletRequest request, Model model) {
LOGGER.log(Logger.Level.INFO, "URL: " + request.getRequestURL().toString());
model.addAttribute("pageTitle", "Home");
model.addAttribute("messageTitle", "Spring MVC Greetings Application");
model.addAttribute("messageBody", "Welcome to Spring MVC Greetings Application!");
return "home/index";
}
}
GreetingsController.java
package com.julianjupiter.springmvcgreetings.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
import java.lang.System.Logger;
@Controller
public class GreetingsController {
private static final Logger LOGGER = System.getLogger(GreetingsController.class.getName());
@RequestMapping(value = {"/greetings"}, method = RequestMethod.GET)
public String index(HttpServletRequest request, Model model) {
LOGGER.log(Logger.Level.INFO, "URL: " + request.getRequestURL().toString());
model.addAttribute("pageTitle", "Greetings");
model.addAttribute("messageTitle", "Hello world!");
model.addAttribute("messageBody", "Hello world! Welcome to Spring MVC!");
return "greetings/index";
}
}
Add Static Assets
We’ll be using Bootstrap CSS framework. I only include bootstrap.min.css
, bootstrap.min.js
, jquery-3.5.1.slim.min.js
, and popper.min.js
.
I also have minimal custom CSS style, application.css
:
body {
padding-top: 4.5rem;
}
footer {
bottom:0;
width:100%;
}
Add JSP templates
includes
head.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<jsp:useBean id="date" class="java.util.Date" />
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="Spring MVC Application with Java configuration">
<meta name="author" content="Julian Jupiter">
<title>Spring MVC Greetings Application — ${pageTitle}</title>
<!-- Bootstrap core CSS -->
<link href="${pageContext.request.contextPath}/assets/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="${pageContext.request.contextPath}/assets/css/application.css" rel="stylesheet">
</head>
<body>
header.jsp
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<a class="navbar-brand" href="${pageContext.request.contextPath}/">Spring MVC Greetings Application</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
<li class="${pageTitle == 'Home' ? 'nav-item active': 'nav-item'}">
<a class="nav-link" href="${pageContext.request.contextPath}/">Home <span class="sr-only">(current)</span></a>
</li>
<li class="${pageTitle == 'Greetings' ? 'nav-item active': 'nav-item'}">
<a class="nav-link" href="${pageContext.request.contextPath}/greetings">Greetings</a>
</li>
</ul>
</div>
</nav>
<main role="main" class="flex-shrink-0">
<div class="container">
footer.jsp
</div>
</main>
<footer class="footer position-absolute mt-auto py-3">
<div class="container">
<div class="row">
<div class="col">
<p class="text-muted text-center">© <a href="https://julianjupiter.com"><fmt:formatDate value="${date}" pattern="yyyy" /> Julian Jupiter</a></p>
</div>
</div>
</div>
</footer>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="${pageContext.request.contextPath}/assets/js/jquery-3.5.1.slim.min.js"></script>
<script src="${pageContext.request.contextPath}/assets/js/popper.min.js"></script>
<script src="${pageContext.request.contextPath}/assets/js/bootstrap.min.js"></script>
</body>
</html>
home
index.jsp
<%@ include file="../includes/head.jsp" %>
<%@ include file="../includes/header.jsp" %>
<div class="jumbotron">
<h2>${messageTitle}</h2>
<p>${messageBody}</p>
<p><a class="btn btn-primary btn-lg" href="${pageContext.request.contextPath}/greetings" role="button">Go to greetings »</a></p>
</div>
<%@ include file="../includes/footer.jsp" %>
greetings
index.jsp
<%@ include file="../includes/head.jsp" %>
<%@ include file="../includes/header.jsp" %>
<div class="jumbotron">
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#greetings">Click me »</button>
<div id="greetings" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="myModalLabel">${messageTitle}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
</div>
<div class="modal-body">
<p>${messageBody}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
<%@ include file="../includes/footer.jsp" %>
Build Application
To build our Fat JAR:
$ mvn package
If you’re using Maven wrapper:
$ ./mvnw package
Above command generates executable JAR inside target
folder with file name SpringMVCGreetingsApp-1.0.0-SNAPSHOT.jar
.
Run Application
$ java -jar ./target/SpringMVCGreetingsApp-1.0.0-SNAPSHOT.jar
Above command logs the following in the console:
Jul 26, 2020 7:34:44 PM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-nio-8080"]
Jul 26, 2020 7:34:45 PM org.apache.catalina.core.StandardService startInternal
INFO: Starting service [Tomcat]
Jul 26, 2020 7:34:45 PM org.apache.catalina.core.StandardEngine startInternal
INFO: Starting Servlet engine: [Apache Tomcat/9.0.37]
Jul 26, 2020 7:34:45 PM org.apache.catalina.startup.ContextConfig getDefaultWebXmlFragment
INFO: No global web.xml found
Jul 26, 2020 7:34:48 PM org.apache.catalina.core.ApplicationContext log
INFO: 1 Spring WebApplicationInitializers detected on classpath
Jul 26, 2020 7:34:48 PM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring DispatcherServlet 'dispatcher'
Jul 26, 2020 7:34:48 PM org.springframework.web.servlet.FrameworkServlet initServletBean
INFO: Initializing Servlet 'dispatcher'
Jul 26, 2020 7:34:48 PM org.springframework.web.servlet.FrameworkServlet initServletBean
INFO: Completed initialization in 660 ms
Jul 26, 2020 7:34:48 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-nio-8080"]
Jul 26, 2020 7:34:48 PM com.julianjupiter.springmvcgreetings.server.TomcatServer run
INFO: Application started with URL {}.
Jul 26, 2020 7:34:48 PM com.julianjupiter.springmvcgreetings.server.TomcatServer run
INFO: Hit Ctrl+D or Ctrl+C to stop it...
Embedded Tomcat
listens on port 8080
.
View Application in the browser
Open your browser and point to http://localhost:8080.
Screenshots
Home Page
Greetings Page
View Greetings Modal
Conclusion
We’re able to create a Spring MVC application with Java configuration, no XML.
We build this application into a single JAR file, called Uber or Fat JAR; no need for standalone application server.
Clone the source
$ git clone https://github.com/julianjupiter/SpringMVCGreetingsApp
$ cd SpringMVCGreetingsApp
$ ./mvnw clean package && java -jar ./target/SpringMVCGreetingsApp-1.0.0-SNAPSHOT.jar