Jul 26, 2018 1:00 PM PT

Mastering Spring framework 5, Part 1: Spring MVC

Build a Java web application using Spring MVC with Spring Boot

Jonas Bengtsson (CC BY 2.0)

Spring MVC is the Spring framework's traditional library for building Java web applications. It is one of the most popular web frameworks for building fully functional Java web applications and RESTful web services. In this tutorial, you'll get an overview of Spring MVC and learn how to build Java web applications using Spring Boot, Spring Initializr, and Thymeleaf.

download
Get the source code for example applications in this tutorial. Created for JavaWorld by Steven Haines

Spring Boot with Spring Initializr

We'll fastrack our Spring MVC web application with the help of Spring Boot and Spring Initializr. Given input for the type of application to be built, Spring Initializr uses the most common dependencies and defaults to setup and configure a basic Spring Boot application. You can also add custom dependencies and Spring Initializr will include and manage them, ensuring version compatibility with both third-party software and Spring. Spring Boot applications run standalone, without requiring you to provide a runtime environment.

In this case, since we're building a web application, Spring Boot will automatically include and configure Tomcat as part of the app's runtime. We can also customize the app by adding an H2 database driver to our Maven POM file. Spring Boot will then automatically create an embedded database and DataSource instance in the application context. Once the dependencies are set, Spring Boot will provide default configurations for the application. Of course we can change the configurations if we want to, but thanks to Spring Boot we have a headstart: a fully configured, working application right out of the box.

Once we've selected and configured our dependencies, we'll pass those selections to Spring Initializr, which will provide a downloadable ZIP file containing a base Spring Boot project.

Spring MVC with H2 database engine

We'll start by creating a basic Spring MVC web application that persists data to an H2 embedded database.

Step 1. Setup and configure the app

Navigate to Spring Initializr at start.spring.io and select Generate a Maven Project with Java and Spring Boot 2.0.X, where X is the latest Spring Boot version (2.0.3 at time of this writing). Make sure you select Spring Boot 2.x so that you can implement Spring Web MVC 5. Spring Boot 1.4 and Spring Boot 1.5 will implement Spring 4.

Enter a group name with the format matching your web address, such as com.geekcap.javaworld, and enter an artifact name, such as spring5mvc-example. Figure 1 shows my configuration.

Steven Haines

Figure 1. Config for Spring Initializr

To add dependencies to the web app, you may either enter a comma-separated list of dependencies into the Search for dependencies text field or click on Switch to the full version. We'll take the easier route by clicking on Switch to the full version. The dependencies are divided into groups, such as Core, Web, and Template Engines. For this example, select the checkboxes for: Web-->Web, Template Engines-->Thymeleaf, SQL-->JPA, and SQL-->H2. Here's what each of those selections will add to the application:

  • Web: Spring MVC and Tomcat
  • Thymeleaf: Thymeleaf web template engine
  • JPA: Spring JPA, Hibernate, and Spring Data
  • H2: The H2 embedded database

When you're finished, click the Generate Project button at the bottom of the page. Spring Initializr will create a readymade ZIP file with all the required project sources, which you can download.

Step 2. Import the Spring Initializr project to your IDE

Extract the ZIP file from Spring Initializr, then import the project into your favorite IDE. For example, to import the project into IntelliJ, choose File-->New Project, as shown in Figure 2.

Steven Haines

Figure 2. Importing the project into IntelliJ

Step 3. Setup your Maven POM

Next, navigate to Import project from external module, choose Maven, and press Next. Be sure to choose a Java 1.8 project SDK, then hit Finish.

The Spring Boot starter app

Now let's take a look at the Spring Boot starter application that has been generated by our (minimal) efforts so far.

To start, Listing 1 shows the Maven POM file.

Listing 1. Maven pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<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>com.geekcap.javaworld</groupId>
	<artifactId>spring5mvc-example</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>
	<name>spring5mvc-example</name>
	<description>Demo project for Spring Boot</description>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.3.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

Notice that the POM file uses a special parent POM: spring-boot-starter-parent. We'll use the parent POM to manage the versions of all our dependencies and ensure versions are compatible. The repositories at the end of the POM file reference the Spring snapshot and milestone repositories. We need these because Spring Boot 2.x is still a milestone release at the time of this writing.

The dependencies are quite minimal, and most are prefaced with spring-boot-starter:

  • spring-boot-starter-data-jpa
  • spring-boot-starter-thymeleaf
  • spring-boot-starter-web
  • spring-boot-starter-test

Each of these starter dependencies brings in all of the sub-dependencies it needs. Figure 3 shows the partially expanded dependency view in IntelliJ.

Steven Haines

Figure 3. Spring starter project dependencies

The POM file includes the following dependencies:

  • spring-boot-starter-data-jpa includes Hibernate and Spring Data.
  • spring-boot-starter-thymeleaf includes the Thymeleaf template engine.
  • spring-boot-starter-web includes spring-boot-starter-tomcat, an embedded version of Apache Tomcat.
  • spring-boot-starter-json includes the Jackson JSON libraries.
  • spring-web and spring-webmvc includes Spring MVC.
  • spring-boot-starter-test includes testing libraries such as JUnit and Mockito.

When Spring Boot sees these dependencies in the CLASSPATH, it initiates automatic configuration. For example, when it finds spring-boot-starter-web, it creates an embedded version of Tomcat, and when it finds H2 and spring-boot-starter-jpa it creates an H2 embedded database and a Hibernate EntityManager. It then wires the EntityManager into Spring Data.

Spring Boot also creates a single class that can be used to run the application. The class for the example application is shown in Listing 2.

Listing 2. Spring5mvcExampleApplication.java


package com.geekcap.javaworld.spring5mvcexample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Spring5mvcExampleApplication {
    public static void main(String[] args) {
        SpringApplication.run(Spring5mvcExampleApplication.class, args);
    }
}

This class leverages the SpringApplication.run() method, passing in the class to run (Spring5mvcExampleApplication in this example). The @SpringBootApplication annotation includes the following annotations:

  • @Configuration informs Spring that the Spring5mvcExampleApplication class contains configuration information. (This annotation can be used to create beans that will get registered with the Spring context.)
  • @EnableAutoConfiguration tells Spring to automatically configure resources from dependencies found in the CLASSPATH, such as H2 and Tomcat.
  • @ComponentScan tells Spring to scan packages in the CLASSPATH under the current package (com.geekcap.javaworld.spring5mvcexample) for Spring-annotated components such as @Service and @Controller.

Spring scans the CLASSPATH and automatically creates components such as the embedded Tomcat server and H2 database. It then populates the Spring context with the application components found in the package scan. In essence, Spring Boot makes it very easy to select and configure the services, components, controllers, entities, and so forth that you need for your application. Once you've done that, Spring will automatically find them, make them available in the Spring context, and autowire everything together.

We've got our Spring Boot starter project setup and ready to go. In the next section we'll create the Spring MVC components for our Java web application.

About Spring MVC 5

Spring MVC implements the popular Model-View-Controller pattern, which you've probably seen in other web frameworks. The Model-View-Controller pattern separates concerns into three categories:

  • Model represents your domain objects.
  • View renders your model to a view, such as to an HTML page.
  • Controller sits between your view and model and translates change requests in the view into changes in the model, and vice versa. In practical terms, the controller accepts incoming requests, potentially updates the model, and sends your model objects to a "view" to render back to the client.

In Spring MVC, controllers are identified by the @Controller annotation and accompanied by a @RequestMapping annotation. The annotation defines the HTTP verb (standard HTTP commands like GET, POST, PUT, and DELETE) and URI for which the request-mapping method will be applied. Spring 4 introduced shortcut request mappings, which make things even easier. We'll use these mappings--@GetMapping, @PostMapping, @PutMapping, @PatchMapping, and @DeleteMapping--for our example application.

The model in Spring MVC

For our application, we'll define a simple model object, a Widget, store it in an embedded H2 database, and build a controller to manage widgets. Let's start with the Widget class, which is shown in Listing 3.

Listing 3. Widget.java


package com.geekcap.javaworld.spring5mvcexample.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Widget {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;
    private String description;
    public Widget() {
    }
    public Widget(Long id, String name, String description) {
        this.id = id;
        this.name = name;
        this.description = description;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
}    

The Widget class is a plain old Java object (POJO) that is annotated with JPA (Java Persistence API) annotations. In this case there are three annotations:

  • @Entity identifies Widget as an entity that can be persisted to a database.
  • @Id identifies the id field as the primary key of the entity.
  • @GeneratedValue tells the JPA EntityManager that the key should be automatically generated in the database.

Note that EntityManager is created automatically for us because we included JPA in the Spring Initializr.

The Widget class manages three fields:

  • id is the ID, or primary key of the widget.
  • name is the name of the widget.
  • description is a description of the widget.

To persist widgets to and from our embedded database, we need to leverage Spring Data. Basically, we'll define an interface that extends one of Spring Data's interfaces, such as CrudRepository, and Spring Data will provide an implementation of that interface at runtime.

The CrudRepository interface includes the following methods:

  • findById finds the entity in the database with the specified ID.
  • findAll returns all entities of the repository type from the database (note that there are other Spring Data repository interfaces, such as PagingAndSortingRepository, that can help manage larger data sets).
  • findAllById passed a collection of IDs, this method returns all entities for those IDs.
  • save persists an entity to the database (create or update).
  • saveAll saves a collection of entities to the database.
  • delete deletes the specified entity.
  • deleteById deletes the entity with the specified ID.
  • deleteAll deletes all entities managed by the repository.
  • count returns the number of entities that are in the database.
  • existsById returns true if an entity with the specified ID exists in the database.

For our example, we'll create a WidgetRepository interface that extends CrudRepository, as shown in Listing 4.

Listing 4. WidgetRepository.java


package com.geekcap.javaworld.spring5mvcexample.repository;
import com.geekcap.javaworld.spring5mvcexample.model.Widget;
import org.springframework.data.repository.CrudRepository;
public interface WidgetRepository extends CrudRepository<Widget, Long> {
}    

The WidgetRepository allows us to perform all create, read, update, and delete (CRUD) operations on widgets. The two parameters passed to the CrudRepository are Widget, which represents the type of entity that the repository manages, and Long, which is the type of the primary key for the Widget entity. (This is a simple implementation that scratches the surface of what you can do with Spring Data. I encourage you to learn more about this useful Spring project.)

The controller in Spring MVC

With our Widget entity and WidgetRepository in hand, we're ready to build a controller. Listing 5 shows the source code for the WidgetController.

Listing 5. WidgetController.java


package com.geekcap.javaworld.spring5mvcexample.web;
import com.geekcap.javaworld.spring5mvcexample.model.Widget;
import com.geekcap.javaworld.spring5mvcexample.repository.WidgetRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller
public class WidgetController {
    @Autowired
    private WidgetRepository widgetRepository;
    /**
     * Load the new widget page.
     * @param model
     * @return
     */
    @GetMapping("/widget/new")
    public String newWidget(Model model) {
        model.addAttribute("widget", new Widget());
        return "widgetform";
    }
    /**
     * Create a new widget.
     * @param widget
     * @param model
     * @return
     */
    @PostMapping("/widget")
    public String createWidget(Widget widget, Model model) {
        widgetRepository.save(widget);
        return "redirect:/widget/" + widget.getId();
    }
    /**
     * Get a widget by ID.
     * @param id
     * @param model
     * @return
     */
    @GetMapping("/widget/{id}")
    public String getWidgetById(@PathVariable Long id, Model model) {
        model.addAttribute("widget", widgetRepository.findById(id).orElse(new Widget()));
        return "widget";
    }
    /**
     * Get all widgets.
     * @param model
     * @return
     */
    @GetMapping("/widgets")
    public String getWidgets(Model model) {
        model.addAttribute("widgets", widgetRepository.findAll());
        return "widgets";
    }
    /**
     * Load the edit widget page for the widget with the specified ID.
     * @param id
     * @param model
     * @return
     */
    @GetMapping("/widget/edit/{id}")
    public String editWidget(@PathVariable Long id, Model model) {
        model.addAttribute("widget", widgetRepository.findById(id).orElse(new Widget()));
        return "widgetform";
    }
    /**
     * Update a widget.
     * @param widget
     * @return
     */
    @PostMapping("/widget/{id}")
    public String updateWidget(Widget widget) {
        widgetRepository.save(widget);
        return "redirect:/widget/" + widget.getId();
    }
    /**
     * Delete a widget by ID.
     * @param id
     * @return
     */
    @GetMapping("/widget/delete/{id}")
    public String deleteWidget(@PathVariable  Long id) {
        widgetRepository.deleteById(id);
        return "redirect:/widgets";
    }
}

The WidgetController is annotated with the @Controller annotation. When Spring performs a package scan of the classes, it will find the @Controller annotation, create an instance of this class, and add it to the Spring context, configured to handle web requests. The WidgetController defines a WidgetRepository instance that is annotated with the @Autowired annotation. When Spring sees this, it will find the WidgetRepository implementation created by Spring Data and automatically wire the repository into the controller.

The WidgetController then defines a set of methods to handle requests, using the @GetMapping and @PostMapping annotations. These annotations accept the URI path that each method is handling, defining the HTTP verb accordingly. Because we are using the Thymeleaf template engine, each method returns a String naming the template to render. Templates are stored in src/main/resources/templates as HTML files with specific Thymeleaf markup, which is shown below.

The methods that return a direct template name are all passed a Model object. The Model object implements a Spring UI interface and provides methods to add attributes to the model. These attributes will be made available to the template to be rendered. For example, in the getWidgetById method, we retrieve a Widget from the database and set it in the Model with the key "widget". The template can then access that Widget using the widget key.

Controller methods and the view template

Next let's walk through each WidgetController method and its corresponding view template. The homepage for the application is the /widgets page, which shows a list of all widgets in the database. The getWidgets method retrieves all widgets by calling the WidgetRepository's findAll method, sets it as the "widgets" attribute in the model, and returns "widgets". Listing 6 shows the widgets.html template file.

Listing 6. widgets.html


<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Widgets</title>
    <link href="../static/css/bootstrap.min.css" th:href="@{css/bootstrap.min.css}" rel="stylesheet">
</head>
<body>
    <div class="container">
        <table class="table">
            <h2>Widgets</h2>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Description</th>
                <th>View</th>
                <th>Edit</th>
                <th>Delete</th>
            </tr>
            <tr th:each="widget : ${widgets}">
                <td th:text="${widget.id}">Widget ID</td>
                <td th:text="${widget.name}">Widget Name</td>
                <td th:text="${widget.description}">Widget Description</td>
                <td><a class="btn btn-default" href="#" th:href="${'/widget/' + widget.id}">View</a> </td>
                <td><a class="btn btn-default" href="#" th:href="${'/widget/edit/' + widget.id}">Edit</a> </td>
                <td><a class="btn btn-default" href="#" th:href="${'/widget/delete/' + widget.id}">Delete</a> </td>
            </tr>
        </table>
        <a th:href="${'/widget/new'}" class="btn btn-default">New</a>
    </div>
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    <script src="../static/js/bootstrap.min.js" th:src="@{js/bootstrap.min.js}"></script>
</body>
</html>

Thymeleaf templates are HTML files that import the "http://www.thymeleaf.org" XML namespace, as seen in the html tag. With this namespace included (as "th"), Thymeleaf allows us to add tags to existing HTML elements. This enables you to create and style your HTML files without running your application in Tomcat.

For this example, we've added the Twitter Bootstrap library, which makes it easier to create a nice layout. Rather than constantly restarting Tomcat to view pages, this lets us build the page using an editor, then render the page in a browser. In order to work with your JavaScript and CSS locally you also need to include the Javascript href and script source src values that point to your relative CSS and Javascript files, respectively. The th:href and th:src attributes will be resolved to actual URI paths by Thymeleaf in a live application, and will overwrite the href and src attribute values when the page is rendered.

The HTML file in Listing 6 contains a table that shows a summary of each widget, with buttons to view the details of a widget, edit a widget, or delete a widget. We use the model to pass in a collection of widgets, and we access those by adding the th:each attribute to the tr (table row) tag, where th:each is Thymeleaf's implementation of a for-each construct. This will create one row for each item in our collection. The th:each attribute is passed the value "widget : ${widgets}". This retrieves the "widgets" collection from the model and assigns each widget to a local variable, "widget", that will be used inside the row. We can then extract values from the widget using the "${widget.propertyName}" syntax.

For the first three rows, we include the th:text attribute in each table cell (td), extracting fields from the widget. For the last three rows, we add hyperlinks to our other controller URIs:

  • /widget/{id} shows the widget with the specified ID.
  • /widget/edit/{id} shows the widget with the specified ID in an editable form.
  • /widget/delete/{id} deletes the widget with the specified ID, then shows the updated list of widgets.

The bottom of the page adds a New button that allows the user to create a new widget. Widgets are created through the /widget/new URL and presented in the same form as the /widget/edit/{id} URI.

Figure 4 shows a screenshot of the widget list page.

Steven Haines

Figure 4. Widget list page

When the user presses the View button, the getWidgetById method is called:


    @GetMapping("/widget/{id}")
    public String getWidgetById(@PathVariable Long id, Model model) {
        model.addAttribute("widget", widgetRepository.findById(id).orElse(new Widget()));
        return "widget";
    }

This method is mapped to a GET request for /widget/{id} and the id is extracted using the @PathVariable annotation. If you have multiple variables in your path, you can include them using the @PathVariable annotation with a variable name that matches the path variable name. In this case, the getWidgetById method retrieves the widget with the specified ID from the widgetRepository, then assigns it to the model's "widget" property.

You might have noted that the WidgetRepository.findById method returns an Optional<Widget>. Optionals in Java wrap objects: if the object is present then you can retrieve it using the get() method; if the object is not present then the Optional will be equal to Optional.empty(). You can determine if the result has an object by calling the isPresent() method. Alternatively, as I did in this case, you could leverage the orElse() method, which returns the object if it is present. Otherwise, it returns the widget created inside the orElse method. In a production application you would want to return a valid widget or return an error page. For now, we can simply return an empty widget. Finally, this method returns "widget', which tells Spring MVC to render this Widget using the "widget.html" template, shown in Listing 7.

Listing 7. widget.html


<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Widget Details</title>
    <link href="../static/css/bootstrap.min.css" th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
</head>
<body>
    <div class="container">
        <ol class="breadcrumb">
            <li><a href="#" th:href="${'/widgets'}">Home</a></li>
        </ol>
        <div class="row"><h2>Widget Details</h2></div>
        <form class="form-horizontal">
            <div class="form-group">
                <label class="col-sm-2 control-label">ID:</label>
                <div class="col-sm-10">
                    <p class="form-control-static" th:text="${widget.id}">Widget ID</p>
                </div>
            </div>
            <div class="form-group">
                <label class="col-sm-2 control-label">Name:</label>
                <div class="col-sm-10">
                    <p class="form-control-static" th:text="${widget.name}">Widget Name</p>
                </div>
            </div>
            <div class="form-group">
                <label class="col-sm-2 control-label">Description:</label>
                <div class="col-sm-10">
                    <p class="form-control-static" th:text="${widget.description}">Widget Description</p>
                </div>
            </div>
            <div class="form-group">
                <div class="col-sm-offset-2 col-sm-10">
                    <a href="#" class="btn btn-default" th:href="${'/widget/edit/' + widget.id}">Edit</a>
                </div>
            </div>
        </form>
    </div>
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    <script src="../static/js/bootstrap.min.js" th:src="@{/js/bootstrap.min.js}"></script>
</body>
</html>

Aside from the Bootstrap markup, this HTML file shows the three widget fields in paragraph elements using the th:text Thymeleaf attribute with the ${widget.propertyName} notation. If you are not familiar with Bootstrap, don't let the form trick you into thinking this is a proper HTML form that accepts fields; it is just a layout paradigm. You can see sample Bootstrap forms here.

The only other two things I added are:

  • A breadcrumb that allows the user to navigate back to the homepage (widget list), using an anchor tag with the th:href attribute that points to the /widgets page.
  • A button (disguised as an anchor tag but rendered as a button using the Bootstrap class btn btn-default CSS) with a th:href attribute pointing to ${'/widget/edit' + widget.id}. This notation performs a String concatenation of /widget/edit/ with the ID of the widget, resulting in something like /widget/edit/1.

An example of this page is shown in Figure 5.

Steven Haines

Figure 5. Widget edit page

If the user presses the Edit button, the editWidget method is called:


    @GetMapping("/widget/edit/{id}")
    public String editWidget(@PathVariable Long id, Model model) {
        model.addAttribute("widget", widgetRepository.findById(id).orElse(new Widget()));
        return "widgetform";
    }

This method maps to the /widget/edit/{id} URI, retrieves the widget from the database, sets it as the model's "widget" attribute, and tells Spring to render it using the "widgetform.html" Thymeleaf template, shown in Listing 8.

Listing 8. widgetform.html


<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Create/Edit Widget</title>
    <link href="../static/css/bootstrap.min.css" th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
</head>
<body>
<h2>Create / Edit Widget</h2>
<div class="container">
    <form th:object="${widget}" th:action="@{/widget}" method="post">
        <input type="hidden" th:field="*{id}" />
        <div class="form-group">
            <label>Name</label>
            <input type="text" th:field="*{name}" class="form-control" placeholder="Name">
        </div>
        <div class="form-group">
            <label>Description</label>
            <input type="text" th:field="*{description}" class="form-control" placeholder="Description">
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
    </form>
</div>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="../static/js/bootstrap.min.js" th:src="@{/js/bootstrap.min.js}"></script>
</body>
</html>

This template introduces a proper Thymeleaf form. To build a form with Thymeleaf, you specify the object that the form is operating on, using the th:object attribute of the form, and then you can access that object's properties using th:field attribute in form elements. These field attributes are accessed using the "*{propertyName}" notation, which refers back to the form's object. In this example, we define a hidden text field to hold the ID (we don't want the user changing the primary key in the database) and we add the name and description fields. At the end of the document we add a submit button, which submits the form to the th:action, which points to "@{/widget}". Notice that we use the @ symbol to reference locations and the $ symbol to reference model objects.

We are able to leverage the "widgetform" for both creating new Widgets and editing existing Widgets by adding an empty Widget object in the newWidget method:


    @GetMapping("/widget/new")
    public String newWidget(Model model) {
        model.addAttribute("widget", new Widget());
        return "widgetform";
    }

Because we pass an empty Widget object through the "widget" model attribute, the form will not fail if a Widget is not present. This allows us to reuse the same form both for creating and editing widgets.

Now that we have our controller and templates defined, we're ready to run the application. To run a Spring Boot application, execute the following Maven command:

mvn spring-boot:run

You should see Spring start Tomcat, create an embedded H2 database with the name testdb and then show a summary of the URI mappings for your WidgetController.

To see your application in action, open a browser to the following URL:

http://localhost:8080/widgets

When you are ready to release and run your application in production, you can build your project with the standard mvn clean install and then run the resultant executable JAR file:


$ mvn clean install
$ java -jar target/spring5mvc-example-0.0.1-SNAPSHOT.jar

Conclusion

Spring MVC is the traditional Spring framework library for building Java web applications. This article introduced you to Spring MVC web development using Spring Boot, Spring Initializr, and the Thymeleaf view rendering engine.

There's lots more to learn about Spring framework 5! Look for the second half of this article, featuring Spring 5's reactive programming library, Spring WebFlux.