, , ,

Spring Boot Web Application – Part 4 – Spring MVC

This is the 4th part of my tutorial series on building a web application using Spring Boot.  In the the last part of the series, we looked at setting up Spring Data JPA for database persistence. In the second part of the series we looked at using Thymeleaf for building the web pages. And we started off in the first part looking at using the Spring Initializr to start our Spring Boot project.

In this part of the series, we tie everything together to provide a working Spring Boot web application. An application which will display data from the database, and allow you to create new records, update existing records, and delete selected records too.

Spring MVC

In this part of my tutorial series for Spring Boot, we’re going to look at setting up a Spring MVC controller to support CRUD operations against the database.

MVC stands for Model, View, Controller. The MVC design pattern is probably the most popular design pattern used when writing code to generate dynamic web content. This design pattern is not limited to Java nor Spring. The MVC design pattern has been applied in Javascript, PHP, .NET, Python, and many other programming languages. The MVC pattern is popular because it does a great job of separating concerns, and leads you to a clean, maintainable, and easy to understand code base.

MVC Overview


Model refers to a data model, or some type of data structure. For example a web page showing a list of products, the ‘model’ would contain a list of product data.


The view layer, in Java frequently a JSP. This will take data from the Model and render the view.


I like to describe the controller as a traffic cop. It will take an incoming request, decide what to do with it, then direct the resulting action. For example, the controller could get a view product request. It will direct a service to get the product data, then direct to the product view and provide the ‘model’ (product data) to the view.

Single Responsibility Principle Applied to MVC

Frequently when dealing with legacy code, I see a lot of leakage between the layers. JSP pages making database calls. Controllers building database connection pools. On one legacy application I recently worked with the JSP pages and controllers were littered with static method calls, which ultimately made a call to an Oracle database. Because of this, the application was impossible to run outside of the application server. The code was so tightly coupled, there were no unit tests in what is a very large code case. Why? You can’t run any of the code under JUnit because of all the embedded static method calls.

In an MVC application each component has a specific function in life. You should be able to unit test your controllers. Using Mocks, you should be able to unit test that your controller returns the proper model, and makes the proper decisions.

CRUD Operations with Spring MVC

CRUD is a common acronym for Create, Read, Update, and Delete. In the last part of the series, we looked at creating a CRUD repository using Spring Data JPA. In this post, we’ll look at setting up the Spring MVC controller for the corresponding CRUD operations. We’ll continue using the Product class we previously used.


The Create operation is a two step operation. The first step needs to display the create form, the second needs to do the save of the form post.

Here is the controller code for displaying the create product form.

The @RequestMapping  annotation maps the url ‘product/new’ to this controller action. Our controller method is taking in the model attribute. This is the ‘model’ being returned to the view layer.

You can see in the code, we are returning an empty product class to the view. This is more of a trick to re-use the view code for both the Create and Update form. By providing a empty Product object, we reduce the likelihood of null pointer errors when rendering the view. You can either provide an empty object to the model, or do a lot of null checking in the view. From experience, I’ve found this simpler.

Our create view is going to have a form post. We need a controller action to handle this.

In this controller method, we’re handing the form post. The @RequestMapping annotation says to take the ‘url’ ‘product’ and the HTTP request method of POST to map it to this controller method. You can see how we’re asking for a Product object as input to the controller method. One of the cool things about Spring MVC is that it will take your form parameters and automatically bind them to a Product object. The object is automatically created and passed into your controller method. The Spring Framework saves you from the mundane work of parsing out HTTP request parameters.

You can see how we’re using a product service to handle the persistence. This is just a facade to the Spring Data JPA repository we created in the last post. I’m going to skip over the persistence code here. You’ll be able to find it in github. I want you to notice how I’m writing to an interface. The controller doesn’t know about persistence. It does not need to. Storing data is not the job of the controller. Maybe that method is using JDBC. Maybe it is calling a web service. Maybe it’s using JMS. Might be using AQMP. The controller does not care. The controller code does not need to care. This is a great example of decoupling code. Too often I see legacy code where the controllers are doing way too much.

On the last line of the saveProduct method, you can see I’m returning a string with “redirect”. This tells Spring after the save action to redirect to the view to show the created item. This example just show the ‘happy path’ – where everything happens as it should. In a more robust controller you’d have logic not only for the happy path, but to redirect to the create form if validations were to fail.


In Read operations, the client is going to tell you what it wants. In our case, the client will give us an Id value, and we’ll return the corresponding Product.

Read by Id

You can see in our controller method, the Request Mapping is using ‘product’ with a id value in squigglies. This identifies that portion of the url path as an ‘id; value.

Now, we’re using a new annotation @Pathvariable to inject the id value from the url path into our controller as the ID variable. Again, we’re accepting the model variable into our controller. We’re asking the product service to get the product, and the result is appended to the model object, which is returned to the view. The controller method returns a string to indicate which view to render.

List All

A common method is also to provide a list view. Normally, you’re going to want to add paging or some type of filter. However, in this example, we just want to show a simple example of listing products from the database.

We’ve mapped this controller method to the URL ‘/products’. We ask the product service for a list of all products and append it to the model attribute “products”. The controller method returns the string ‘products’ to tell Spring MVC to render the products view.


Updates are actions against existing entities. Updates are similar to create actions, where we have two controller actions involved. With a create, we’re showing a form for a new item, while an update is going to be populated with data from an existing item. While this is very similar to the create action, we typically will want a separate controller action to show the edit form to capture the data for the update.

The good news, functionally the save and view of the saved item is the same as the create action.

Here is our save method once more:

You can see we’re using Spring to bind the form post parameters to a Product object, then calling the product service to save the item. Then just like in the save method of the create process, we want to view the saved product, so we redirect to the show product view.


There’s a few different ways to implement a delete action. One of the easiest is to use a url with the ID for the delete action. This can then be implemented on the web forms as a simple URL to click on. Below is the controller action for the delete action.

This method will take in the id value from the URL and pass it to the delete method of the product service. Since we’re not creating or updating a product, a typical course of action is to return to the list view. In this example, we redirect to the products view to show the user a list of products.

Summary of CRUD Operations

At this point we’ve covered the necessary controller actions to support CRUD operations on an entity. You can see these operations work in conjunction with the Spring Data JPA methods we looked at in the previous post on Spring Data JPA. I’m using a Facade Service to mask the Spring Data JPA implementation. We’ll take a look at the Facade in the next section.

Free Spring Tutorial

Spring Facade Service

You can see in the controller methods above, there is no dependency on the persistence layer. The controller is completely unaware of how data is being persisted. This is exactly as it should be. Too often I see legacy code where the controller is interacting with the database directly. This is a very poor coding practice. It makes your code tightly coupled and hard to maintain.

Code to an Interface

When using Spring to develop applications it is always best to develop to an interface, especially when leveraging the benefits of dependency injection.  To support our controller actions, I wrote the following interface.


Notice how this interface is rather generic? Can you tell how data is being persisted? JDBC? Spring Data JPA? Web Service? JMS? This is what decoupling is about. At this point, the answer is all of the above. We just need to provide the appropriate implementation.

Spring Data JPA Product Service Implementation

In the last post of this series, we looked at using Spring Data JPA. Now we need an implementation of the Product Service which will use the Spring Data JPA repositories.

Spring Data JPA Repository

We’ll need to inject an instance of the Spring Data JPA repository into the implementation of our product service. You can do so by declaring a property for the repository and annotating the setter method with the @Autowired  annotation.

List Products

Using Spring Data JPA, it becomes trivial to list all the products for our application. While we did not actually create a findAll()  method on the repository we defined, we inherited by extending the CrudRepository  in Spring Data JPA. This is one of many handy features of Spring Data JPA. It’s going to provide us an implementation of the findAll()  method, which we do not need to write code for.

Get Product (Read)

To fetch a product by its id value, again, we can leverage a method implemented for us by Spring Data JPA.

Save Product (Create / Update)

Spring Data JPA also provides us an implementation of a save method for saving entities. We use this method in creating and updating products in our web application.

Delete Product (Delete)

Finally, in our CRUD operations, Spring Data JPA provides us an implementation of a delete method. Spring Data JPA overloads the delete method, accepting just the ID value, or the entity itself. For our purposes, we are using the ID value to delete the desired entity.

Summary of Spring Data JPA Usage

In this example, we implemented the CRUD operations using a CrudRepository supplied by Spring Data JPA. If you look at the code you will see all we did was extend the Spring Data JPA CrudRepository to create our Product Repository. We did not define, nor implement an additional methods. We’re not declaring transactions. We’re not writing any SQL. I hope you can see the simplicity and time saving using tools like Spring Data JPA can bring you.


Thymeleaf Fragments

Thymeleaf fragments are a very powerful feature of Thymeleaf. They allow you to define repeatable chunks of code for your website. Once you define a Thymeleaf fragment, you can reuse it in other Thymeleaf templates. This works great for components you wish to reuse across your web pages.

In developing the Spring Boot Web Application, I found two uses for Thymeleaf templates. The first was common includes of the CSS, Javascript. The second was for a common menu I wanted to display at the top of each web page.


Below is the Thymeleaf Fragment I’m using for the HTML header includes. You can see its a normal HTML document, using Thymeleaf tags to define the resources for a page.



For our Spring Boot Web Application, I chose to use the Bootstrap CSS framework. I’m big fan of Bootstrap. It’s easy to use, and its components look great. Bootstrap CSS has a menu component which I chose to use for the menu system.

In this Thymeleaf fragment, I’m providing the Bootstrap CSS menu I want to place at the top of all my pages. I also have a section to show my Spring Boot logo on each page.


Including Thymeleaf Fragments


Previously, we defined an index page for our Spring Boot web application. You can apply Thymeleaf templates through the use of HTML comments. By doing this, you preserve the ability of the document to be viewed in the browser. You will be able to see the document okay in your browser, but the fragment portions will be omitted. The fragments are only included when the Thymeleaf template is rendered by Spring.

Remember, Spring will be reading the Thymeleaf templates, then producing output based upon the Thymeleaf directives.


You can see how our index page is very simple now. While this is a very lean HTML document, when Spring renders it at run time, you will see HTML looking like this:

Actual HTML Rendered to Browser

Notice how Thymeleaf and Spring have merged the contents of the index.html document and the two Thymeleaf fragment documents? Now you have pure HTML, and Thymeleaf tags are not rendered to the HTML content sent to the browser.

The index.html Thymeleaf template will show this page in your browser.

Thymeleaf Index page

Thymeleaf Views for CRUD Application

Show Product

Showing a product is one of the simpler operations under Spring MVC and Thymeleaf. Our controller returned a product object to the model and bound it to the property ‘product’.  Now we can use the typical name-dot-property syntax to access properties of the product object.

This Thymeleaf tag:

Will get text from the description property of the product object and replace the ‘description’ text in the paragraph HTML tag.

Here is the full Thymeleaf template for showing a product:


The show product Thymeleaf template will show this page:

Thymeleaf Show Product Page

List Products

The list view is a little trickier because now we have a list of products to iterate over. Luckily, Thymeleaf makes this very easy to do.

Here is a snippet showing how to iterate over a list of products.

You can see the syntax of this Thymeleaf tag is similar to a for-each loop in Java.

Our controller added a list of products to the ‘products’ property to the model, which we pass to the Thymeleaf tag. The variable name we are assigning to the iterator is ‘product’.

The body of the each tag will be rendered once for each product in the list of products.

Here is the complete Thymeleaf template used for showing a list of products.


Here is the Thymeleaf list products page:

Thymeleaf List Products

Create / Update Product

We can use the same HTML form for creating and updating products. A little trick is to have your controller method return an empty object to the view for the create option, and the existing object for the update option. By doing this you don’t need to worry about null objects on the view layer. For a new object, the null properties show up blank. For existing objects, non-null properties will get populated into the form fields.

The following line sets up the form in Thymeleaf.

The “th:object” tag binds the product object to the form. Thus, you only use the property names on the form fields. No need to qualify the object name too.

The “th:action” tag maps the form action to the ‘/product’ url. And we specify to use the HTML post action for the form.

Here is the controller action this maps back to:

Notice how we’ve assigned the url ‘product’ and method POST in the request mapping.

This next step is critical for your updates to work properly. All entities have an ID value. This is not accessible for the user to edit, but it still needs to be included to the post back to the server, so Spring / Hibernate can find the correct entity to update. If this is missing, there is no way to distinguish between and update and a create. If the ID property is missing from the form post, Spring Data JPA will think it’s a new item and create a new entity.

The way to handle this is through the use of hidden form fields. In this snippet, we’re assigning hidden fields for the Id and version values. (A best practice in Hibernate is to use a version property to detect conflicting updates.)

Here is the complete product form.


Here is the Thymeleaf product form.

Thymeleaf Product Form

Thymeleaf Spring Course
Want to learn more about Thymeleaf, check out my Thymeleaf Spring Course!


In this post we built upon the previous posts in this series on building a web application using Spring Boot to have a functional web application which performs CRUD operations against a single entity. At this point, you can checkout the project from Github and build it using Maven. Spring Boot will create an executable JAR, which you can run to demo the application. Spring Boot will run the application in an embedded Apache Tomcat instance and you will be able to see the application running at http://localhost:8080.

In the next part of this series, I’ll show you how to secure content using Spring Security.

Free Introduction to Spring Tutorial

Are you new to the Spring Framework? Enroll in my free Introduction to Spring Course!

Get The Source!

Like all of my tutorials, the source code for this post is available on GitHub here.



You May Also Like

64 comments on “Spring Boot Web Application – Part 4 – Spring MVC

  1. Hello,Springframework Guru,Really thank you for the wonderful tutorials.
    I was searching for such a tutorials with tutor having real world programming experience and i guess you are here.
    Thanks alot.

    • Thanks!!

      • Are There any Tutorials along with Spring Security with jpa for examples Simple Login using mysql with multiple user roles coming up in near future..
        Hoping for it.. 🙂

        • Yes – I’m planning to do something with Spring Security next in this series.

          • cool.You surely gonna rock it

  2. Big thanks for another good tutorial! Keep on going 🙂

    I have one question too – why are fragments commented out from code, and how they work like that?
    For example, in index.html the commented out fragments work, and just plain
    will work too.
    Intellij will only show errors though, because it wont understand tags when fragment without xmlns declaration.

    Thanks alot

    • The fragments are referenced as html comments so browsers will ignore them when viewing the HTML directly. But are picked up by Thymeleaf when rendered.

  3. (th:block th:include=”fragments/headerinc :: head”)(/th:block)
    replaced < with simple parenthesis 🙂

  4. Nice one!

  5. Do you have a good guide to make it works with Angular JS ?
    i got my Front-end in Angular JS consuming the Web Service RestFUL (Spring Boot / MVC / Hibernate).
    From this, i would be glad to make my CRUD works.

    • Using @RestController instead of @Controller. I guess it’s not that simple to make something as generic as you previous example.

  6. No I don’t. I’m using Angular JS and Grails for client project. Setting up the rest stuff under Spring MVC instead of Grails would be fairly trivial. Maybe I’ll do a post on that in the future.

  7. I think you are a angel

  8. Really awesome!

    Especially the trick with the H2 Console: magic. It’s really helpful.
    Thank you so much and keep on going.


  9. Hello jt,
    just a little remark: in productform.html I miss
    Without this, when editing a product, we lose the value of productId.

    In the ProductServiceImpl we may set the productId before saving the “new” product:
    public Product saveProduct(final Product product) {
    if (product.getProductId() == null || product.getProductId().trim().isEmpty()) {
    return productRepository.save(product);

    Thank you again!

    • Meziane, I think the real problem was a section missing in the productform.html:

      Product Id:

    • Meziane, what value are you sending to the product.setProductId()

  10. Hello nice tutorial!!! It helps me a lot. Could you write about spring boot and restull api? Thanks

  11. Are There any Tutorials along with Spring Security with jpa for examples Simple Login using mongodb with multiple user roles coming up in near future..
    Hoping so..thanks

  12. Tremendous post, I’m from Peru and I was looking for a post that uses Spring MVC and Thymeleaf.
    I found a problem in your products.html, you write this:


    But for to have a relative path, you need to use:


    Sorry for my bad english :p

    • Sorry …the html code that I attached wasn’t posted.

      th:href=”${‘/product/’ + product.id}”>View

      Relative path:
      th:href=”@{‘/product/’ + ${product.id}}”>View

      • Arthur, could you please post the whole section? Thanks.

  13. Muchas gracias, excelente tutorial sobre spring!

    • Thanks!!

  14. Really excellent tutorial. I have a question though. While everything is working fine and the pages are displaying properly, I am getting the following message in my console window in Eclipse Tool Suite :
    2015-11-28 16:44:28.741 ERROR 10221 — [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause

    java.lang.NullPointerException: null
    at java.io.FilterInputStream.read(FilterInputStream.java:133)
    at java.io.FilterInputStream.read(FilterInputStream.java:107)
    at org.springframework.util.StreamUtils.copy(StreamUtils.java:123)

    • Not sure what the cause is. I can’t recreate with my code.

  15. Also, on the productshow.html is it necessary to have the tag ?

  16. the from tag i mean

    • sorry, I don’t understand the question

  17. Great tutorial!

    I have a question regarding the update functionality. When I edit a product and click the Submit Button a new product will be created, instead of the old one being replaced. How can I change this? What should I do?

    • Thanks! Verify you have the id and version as hidden properties in the form for the submit. Sounds like Hibernate can’t find the existing object.

      • Thanks for the reply.
        But with the tutorial code the update functionality isn’t implemented, is it? You would have to set the attributes of the product and then save it. Or am I missing something?
        Where in the code is it written that by clicking “Create” (after clicking on edit and changing some text), the entity should be replaced and not a new one created?

        • HIbernate uses the ID property to determine if the entity is new or already persisted. A new entity will have a null id.

  18. Thanks for this great guide! I have a question about the menu, I got menu as below, can you tell me how to solve it?

    • Thanks – looks like your CSS isn’t resolving.

      • I found reason because I import the newest jar in pom.xml so that they are not the same version as in header.html.
        By the way, I found another problem when I try to use MySQL, I checked with your source, they don’t have difference, especially for files pom.xml, application.properties and so on, but my project show warning as below, though I can run project without problems, do you know why?

        WARN: Establishing SSL connection without server’s identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn’t set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to ‘false’. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.

  19. I am writing a web application using thymeleaf, springboot using your demo. I have one model class with id, name, address, state and district field, state and district are list of string data. I have 3 tables user table, state table with id, state_name, state_code, and district table with id, district_code, district_name, state_code.

    Q1. When I load my newrecord view it should fatch all record from state table and populate in select element?.

    Q2. When I select state from select fatch all dstrict list of that state?.

    Q3. When I open same record in edit mode state and district list show its default value?.

  20. Great series of articles. Got a stubbed out web app running in about an hour. One thing I would mention is that you didn’t specify where to put the fragments html files. You need to create a directory called fragments under the templates directory and put them in there. Took mea fe minutes to figure that one out

  21. Excellent tutorial- thank you!

    One minor correction (of a typo): In the “Show Product” paragraph, although the text is talking about the product description, the code sample is for product.id.

    Thank you again.

  22. Great tutorial thanks.
    I got lot knowledge using this.
    Is there any thing on Spring Security with mysql or any DB for examples Simple Login role base application using mysql with multiple user roles like admin or user.

    • I don’t have there here (yet, at least), but I did add Spring Security with DB authentication to my Spring Core course. I just recorded the module yesterday, and will be publishing it to the course in the next day or so.

  23. Thanks for your great effort.
    How can I get this video?.

  24. Enroll in my Spring Core course – http://courses.springframework.guru/courses/spring-core

  25. thanks,, it’s really helpful,,!

  26. Where can i get the css stylesheet?

  27. Hi,

    Is it possible to get a good tutorial (like yours) on Spring boot sessions using custom entities instead of the default User to login?

    Thank you very much for the useful tutorials.

    • Yes, I hope to publish that in the near future.

  28. John,
    The id gets lost in the update

  29. Very good tutorial, thanks for sharing!

  30. How much we have to alter if we are not using JPA or h2db, instead we are using Mongdb? I followed your tutorial, My update and delete functions does not work.

  31. thanks for your insightful material!
    I am confused by the syntax of:
    <!–/*/ /*/–>
    what is the differences with


    • That’s a Thymeleaf comment syntax to get the fragment included when rendered, but to have it ignored when viewed directly in the browser.

  32. Great tutorial, honestly I learned a lot from your blog

    One question regarding controllers above – is it some “convention over configuration” that you don’t specify http methods (for delete method = RequestMethod.DELETE / also for edit (PUT or POST) ???

    • No – If you don’t specify a request method, then the controller will pickup any method.

      • does it mean that we (or someone else with some despicable intentions …) can simply paste the url link ../product/delete/{and id number} to delete the record (sort of sql injection…). Should’t be made with http delete method where request and id are separated? Please advise

        • Well of course… But the HTTP method isn’t going to change any of that. This isn’t SQL injection at all. Its a simple API call. The ID value is a parameter of the API call. Securing the API call is a whole different subject & the realm of Spring Security.

  33. A very helpful tutorial! I was wondering about JPA and extension… If Hibernate automatically makes these entities, how would you handle if you wanted to make: “class Group” (has id/title/description) and “class SubGroup extends Group” (with its own id/title/description)?

    Basically there can be many SubGroups for every one Group. Is there a way to easily accomplish this?

  34. Very helpful .Thanks for Posting . Keep it up and all the very best 🙂

  35. Thanks Guru for your tutorial, it’s a good help.
    But, I have one question :
    When I try to add some product by my form, system signal me this error :
    “There was an unexpected error (type=Method Not Allowed, status=405).
    Request method ‘GET’ not supported”.

    Can you help to resolve this problem ? Thanks !

    • Check the post method in your HTML

  36. Thank you so much Guru for this tutorial. However, after following all the four series in this tutorial diligently and ran the code, the only thing that the browser presented me with is just a white website that displays index. I also copied and pasted the code to strat again and to my amazement, the result was the same.

    Is there anything am doing wrong or that I need to do to tomcat so that it site is displayed.


    • Hi Samuel,

      Yes, you have something wrong. Hard to say what from what you’ve shared. Check the console for error messages. I suspect you’re getting an exception.

      • Thank you so much JT for your response. Yes I got the system working. But somehow, the ProductLoader is not getting called. Hence the system is not inserting the data and only the H2 Console is getting displayed. Others are not. Besides there are some configuration that I need to do before the system could work. For instance the security configuration does not work out of the box. I can do a short video and sent it to you, although, I am moving on

Leave a Reply