Main Tutorials

Spring MVC hello world example (Gradle and JSP)

This tutorial shows you how to create a Spring Web MVC application with the Jakarta Server Pages (JSP; formerly JavaServer Pages) template.

Technologies and tools used:

  • Java 11
  • Spring 5.2.22.RELEASE
  • JSP
  • JSTL 1.2
  • Servlet API 4.0.1
  • Bootstrap 5.2.0 (webjars)
  • IntelliJ IDEA
  • Gradle 7.5.1
  • Gradle Gretty plugin 3.0.9 for embedded servlet containers (Tomcat 9 and Jetty 9.4)
  • Spring Test 5.2.22.RELEASE
  • Hamcrest 2.2
  • JUnit 5.9

Table of contents:

Note
This tutorial is NOT a Spring Boot application, just pure Spring Web MVC!

1. Directory Structure

Below is the standard Java directory structure for this project.

directory structure

2. Project Dependencies

Below is the core dependency for this project; The spring-webmvc dependency is a must, but other dependencies depend on your project requirement.

  • spring-webmvc – For Spring core related web components.
  • jstl – For Jakarta Standard Tag Library (JSTL; formerly JavaServer Pages Standard Tag Library).
  • org.webjars.bootstrap – For WebJars to manage client-side web libraries, for example bootstrap.
  • javax.servlet-api – We need servlet-api to compile the web application, set to compileOnly scope; usually, the embedded server will provide this.
  • org.gretty – Run this project with the embedded servlet containers like Tomcat 9 or Jetty 9.4.

Why Gretty 3 plugin?
The latest Gretty 4 plugin supports only Tomcat 10, and we need Tomcat 8.5 or 9 for Spring MVC 5, so we will stick with the older Gretty 3.

Why Spring Web MVC 5 does not run o Tomcat 10?

  • Tomcat 10 implements the Servlet 5 specification (part of Jakarta EE 9), under package jakarta.*.
  • Spring Web MVC 5.x implements the Servlet 4 specification (part of Jakarta EE 8) , under package javax.*.

Spring 5 still depends on javax.servlet.*, and the latest Tomcat 10 depends on jakarta.servlet. In a nutshell, Spring 5 does not works with Tomcat 10 because of the package renaming from javax.* to jakarta.*.

build.gradle

plugins {
	id 'org.gretty' version '3.0.9'
}
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'idea'

// JDK 11
sourceCompatibility = 11
targetCompatibility = 11

repositories {
    mavenCentral()
}

dependencies {
	compileOnly 'javax.servlet:javax.servlet-api:4.0.1'
	implementation 'javax.servlet:jstl:1.2'
	implementation 'org.springframework:spring-webmvc:5.2.22.RELEASE'
	implementation 'org.webjars:bootstrap:5.2.0'
	testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
	testImplementation 'org.springframework:spring-test:5.2.22.RELEASE'
	testImplementation 'org.hamcrest:hamcrest-core:2.2'
}

gretty {
	httpPort = 8080
	contextPath = '/'
	//servletContainer = 'jetty9.4'
	servletContainer = 'tomcat9'
}

test {
	useJUnitPlatform()
}

3. Project Dependencies – Tree Format

Review again the project dependencies in a tree structure.

Terminal

gradle dependencies --configuration compileClasspath

> Task :dependencies

------------------------------------------------------------
Root project 'spring-mvc-hello-world-jsp'
------------------------------------------------------------

compileClasspath - Compile classpath for source set 'main'.
+--- javax.servlet:javax.servlet-api:4.0.1
+--- javax.servlet:jstl:1.2
+--- org.springframework:spring-webmvc:5.2.22.RELEASE
|    +--- org.springframework:spring-aop:5.2.22.RELEASE
|    |    +--- org.springframework:spring-beans:5.2.22.RELEASE
|    |    |    \--- org.springframework:spring-core:5.2.22.RELEASE
|    |    |         \--- org.springframework:spring-jcl:5.2.22.RELEASE
|    |    \--- org.springframework:spring-core:5.2.22.RELEASE (*)
|    +--- org.springframework:spring-beans:5.2.22.RELEASE (*)
|    +--- org.springframework:spring-context:5.2.22.RELEASE
|    |    +--- org.springframework:spring-aop:5.2.22.RELEASE (*)
|    |    +--- org.springframework:spring-beans:5.2.22.RELEASE (*)
|    |    +--- org.springframework:spring-core:5.2.22.RELEASE (*)
|    |    \--- org.springframework:spring-expression:5.2.22.RELEASE
|    |         \--- org.springframework:spring-core:5.2.22.RELEASE (*)
|    +--- org.springframework:spring-core:5.2.22.RELEASE (*)
|    +--- org.springframework:spring-expression:5.2.22.RELEASE (*)
|    \--- org.springframework:spring-web:5.2.22.RELEASE
|         +--- org.springframework:spring-beans:5.2.22.RELEASE (*)
|         \--- org.springframework:spring-core:5.2.22.RELEASE (*)
\--- org.webjars:bootstrap:5.2.0

4. Spring Controller

Below is a Spring Web MVC controller to handle web requests for / and /hello/{name} and display messages.

HelloController.java

package com.mkyong.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class HelloController {

    //@RequestMapping(value = "/", method = RequestMethod.GET)
    @GetMapping("/")
    public String defaultPage(ModelMap model) {
        return "hello";
    }

    //@RequestMapping(value = "/hello/{name:.+}", method = RequestMethod.GET)
    @GetMapping("/hello/{name:.+}")
    public ModelAndView hello(@PathVariable("name") String name) {

        ModelAndView model = new ModelAndView();
        model.setViewName("hello");
        model.addObject("message", name);

        return model;

    }

}

5. Spring Configuration

Spring needs a viewResolver bean for JSP page.

SpringWebConfig.java

package com.mkyong.config;

import org.springframework.context.annotation.Bean;
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.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@EnableWebMvc
@Configuration
@ComponentScan({ "com.mkyong.web" })
public class SpringWebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("/webjars/");
    }

    @Bean
    public InternalResourceViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setViewClass(JstlView.class);
        viewResolver.setPrefix("/WEB-INF/views/jsp/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }

}

6. Spring DispatcherServlet

This MyServletInitializer will be auto-detected by the Servlet containers (e.g., Jetty or Tomcat).

MyServletInitializer.java

package com.mkyong;

import com.mkyong.config.SpringWebConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class MyServletInitializer
      extends AbstractAnnotationConfigDispatcherServletInitializer {

  // services and data sources
  @Override
  protected Class<?>[] getRootConfigClasses() {
      return new Class[0];
  }

  // controller, view resolver, handler mapping
  @Override
  protected Class<?>[] getServletConfigClasses() {
      return new Class[]{SpringWebConfig.class};
  }

  @Override
  protected String[] getServletMappings() {
      return new String[]{"/"};
  }
}

7. Spring MVC and Unit Tests

A simple unit test for the Spring MVC controller.

HelloControllerTest.java

package com.mkyong;

import com.mkyong.config.SpringWebConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.ModelAndView;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration(classes = {SpringWebConfig.class})
public class HelloControllerTest {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @BeforeEach
    void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    @Test
    public void testDefaultPage() throws Exception {

        MvcResult result = this.mockMvc.perform(get("/"))
                /*.andDo(print())*/
                .andExpect(status().isOk())
                .andReturn();

        ModelAndView modelAndView = result.getModelAndView();
        assertEquals("hello", modelAndView.getViewName());
        assertNull(modelAndView.getModel().get("message"));

    }

    @Test
    public void testHelloPage() throws Exception {

        MvcResult result = this.mockMvc.perform(get("/hello/mkyong"))
                .andExpect(status().isOk())
                .andReturn();

        ModelAndView modelAndView = result.getModelAndView();
        assertEquals("hello", modelAndView.getViewName());
        assertEquals("mkyong", modelAndView.getModel().get("message"));

    }

}  

8. JSP and JSTL

A simple JSP + JSTL template to display a hello world message also shows how to integrate webjars’ Bootstrap and custom CSS and JS files.

webapp/WEB-INF/views/jsp/hello.jsp

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Spring Web MVC and JSP</title>

<spring:url value="/resources/core/css/main.css" var="coreCss" />
<spring:url value="/webjars/bootstrap/5.2.0/css/bootstrap.min.css" var="bootstrapCss" />

<link href="${bootstrapCss}" rel="stylesheet" />
<link href="${coreCss}" rel="stylesheet" />
</head>

<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
  <a class="navbar-brand" href="#">Mkyong.com</a>
</nav>

<main role="main" class="container">

  <div class="starter-template">
      <h1>Spring Web MVC JSP Example</h1>
      <h2>
            <c:if test="${not empty message}">
                Hello ${message}
            </c:if>

            <c:if test="${empty message}">
                Welcome!
            </c:if>
      </h2>
  </div>

</main>

<spring:url value="/resources/core/js/main.js" var="coreJs" />
<spring:url value="/webjars/bootstrap/5.2.0/js/bootstrap.min.js" var="bootstrapJs" />

<script src="${coreJs}"></script>
<script src="${bootstrapJs}"></script>

</body>
</html>

9. Demo

Go to the terminal, project folder, and run gradle tomcatRunWar.

Terminal

gradle tomcatRunWar

//...
INFO: Completed initialization in 628 ms
17:19:14 INFO  Tomcat 9.0.58 started and listening on port 8080
17:19:14 INFO   runs at:
17:19:14 INFO    http://localhost:8080

> Task :tomcatRunWar
Press any key to stop the server.

P.S Try to run the Spring Web MVC on the embedded Jetty with the command gradle jettyRunWar.

http://localhost:8080/

spring web mvc hello world JSP demo 1

http://localhost:8080/hello/mkyong

spring web mvc hello world JSP demo 2

10. Download Source Code

$ git clone https://github.com/mkyong/spring-mvc/

$ cd spring-mvc-hello-world-jsp

$ gradle tomcatRunWar

or

$ gradle jettyRunWar

visit http://localhost:8080/

visit http://localhost:8080/hello/mkyong

10. References

About Author

author image
Founder of Mkyong.com, love Java and open source stuff. Follow him on Twitter. If you like my tutorials, consider make a donation to these charities.

Comments

Subscribe
Notify of
4 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
uck
8 years ago

thanks, it help me up!

skloke
8 years ago

I don’t understand why view -> jsp -> index.jsp file and directory structure is under WEB-INF? Is this normal practice? Thank you (and thank you for all your tutorials).

vvvvlllll
8 years ago
Reply to  skloke

Yes, this is normal practice. Usually WEB-INF folder is hidden for accessing on server from file manager, for example, but accessible from application. It’s trick for increase security.