Home | Source for Kata

Building Simple Web Service with Spring Boot

Make sure you have your development environment configured.

In this exercise you are going to create a simple REST web service using Spring Boot.

You can skip this step if you feel you have a good understanding of Spring Boot. The rest of the course assume you have a good knowledge of the principles of Spring Boot.

Step-by-step instructions to bootstrap the project

  1. Go to Spring Starter https://start.spring.io/

  2. Select the following options:

    1. Gradle Project

    2. Spring Boot 1.3.2

    3. Group: msvcdojo

    4. Artifact: mysvc

    5. Select in "Search for Dependencies":

      1. Web

      2. Actuator

      3. Actuator Docs

    6. Click button Generate Project

  3. Download project and expand the archive

  4. Open in IntelliJ as a Gradle project

Let’s explore. modify and configure

The project contains an entry point in the MysvcApplication.java file:

MysvcApplication.java
@SpringBootApplication
public class MysvcApplication {

    public static void main(String[] args) {
                SpringApplication.run(MysvcApplication.class, args);
        }
}

We’re going to add a basic REST controller by creating the following class in MysvcApplication.java file:

MysvcApplication.java
@RestController
class HomeController {

    @RequestMapping("/")
    String home() {
        return "Hello World!";
    }
}

Going forward we’re going to use YAML files to provide properties for our services. Rename the mysvc/src/main/resources/application.properties file into mysvc/src/main/resources/application.yml. Let’s expose additional information in our healthcheck output. Put the following in the application.yml file:

application.yml
server:
  port: 8100

endpoints:
  health:
    sensitive: false
  shutdown:
    enabled: true

Let’s replace Tomcat, that was provided as a default application container, with Jetty. Replace the following line in mysvc/build.gradle:

compile('org.springframework.boot:spring-boot-starter-web')

With the following block:

build.gradle
compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") {
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
}
compile ('org.springframework.boot:spring-boot-starter-jetty') {
        exclude group: 'org.eclipse.jetty.websocket'
}

And add the Actuator and the Actuator Docs lines:

build.gradle
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-actuator-docs')

What about tests?

Let’s add a unit test that validates the controller:

HomeControllerTest.java
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MockServletContext.class)
@WebAppConfiguration
public class HomeControllerTest {

        private MockMvc mvc;

        @Before
        public void setUp() throws Exception {
                mvc = MockMvcBuilders.standaloneSetup(new HomeController()).build();
        }

        @Test
        public void getHello() throws Exception {
                mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
                                .andExpect(status().isOk())
                                .andExpect(content().string(equalTo("Hello World!")));
        }
}

Let’s add some integration test:

HomeControllerIT.java
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MysvcApplication.class)
@WebAppConfiguration
@IntegrationTest({"server.port=0"})
public class HomeControllerIT {

    @Value("${local.server.port}")
    private int port;

    private URL base;
    private RestTemplate template;

    @Before
    public void setUp() throws Exception {
        this.base = new URL("http://localhost:" + port + "/");
        template = new TestRestTemplate();
    }

    @Test
    public void getHello() throws Exception {
        ResponseEntity<String> response = template.getForEntity(base.toString(), String.class);
        assertThat(response.getBody(), equalTo("Hello World!"));
    }
}

We’d like to ask JUnit to generate reports for us. Add the following to build.gradle:

build.gradle
test {
    testLogging {
        afterSuite { desc, result ->
            if (!desc.parent) {
                println "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)"
            }
        }
    }
    reports {
                junitXml.enabled = false
                html.enabled = true
        }
}

Now it’s time to run the tests:

$ gradlew test
...
Results: SUCCESS (2 tests, 2 successes, 0 failures, 0 skipped)

And, in case you’d like to get a Web report, open the following file in your browser:

build\reports\tests\index.html

Build and launch

Build the service:

$ cd mysvc
$ gradlew build

Note that this command will build the code and run all the necessary tests.

Lauch it the service:

$ java -jar build\libs\mysvc-0.0.1.jar

The following output you’ll on the screen.

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.3.2.RELEASE)

2016-01-23 [...] msvcdojo.MysvcApplication                : Starting MysvcApplication on DESKTOP-OF8L4HQ with PID 14404 (C:\Dev\accordance\microservice-dojo\kata1\solution\mysvc\build\classes\main started by igorm in C:\Dev\accordance\microservice-dojo\kata1\solution\mysvc)
2016-01-23 [...] msvcdojo.MysvcApplication                : No active profile set, falling back to default profiles: default
2016-01-23 [...] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@548a102f: startup date [Sat Jan 23 15:05:00 EST 2016]; root of context hierarchy
2016-01-23 [...] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'beanNameViewResolver' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2016-01-23 [...] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'managementServletContext' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.actuate.autoconfigure.EndpointWebMvcHypermediaManagementContextConfiguration; factoryMethodName=managementServletContext; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/actuate/autoconfigure/EndpointWebMvcHypermediaManagementContextConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration; factoryMethodName=managementServletContext; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.class]]
2016-01-23 [...] org.eclipse.jetty.util.log               : Logging initialized @3152ms
(1)
2016-01-23 [...] e.j.JettyEmbeddedServletContainerFactory : Server initialized with port: 8100
2016-01-23 [...] org.eclipse.jetty.server.Server          : jetty-9.2.14.v20151106
2016-01-23 [...] application                              : Initializing Spring embedded WebApplicationContext
2016-01-23 [...] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2438 ms
2016-01-23 [...] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2016-01-23 [...] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'metricFilter' to: [/*]
2016-01-23 [...] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'characterEncodingFilter' to: [/*]
2016-01-23 [...] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2016-01-23 [...] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2016-01-23 [...] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'requestContextFilter' to: [/*]
2016-01-23 [...] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'webRequestLoggingFilter' to: [/*]
2016-01-23 [...] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'applicationContextIdFilter' to: [/*]
2016-01-23 [...] o.e.jetty.server.handler.ContextHandler  : Started o.s.b.c.e.j.JettyEmbeddedWebAppContext@150d80c4{/,file:/C:/Users/igorm/AppData/Local/Temp/jetty-docbase.1081708721977203444.8080/,AVAILABLE}
2016-01-23 [...] org.eclipse.jetty.server.Server          : Started @4211ms
2016-01-23 [...] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@548a102f: startup date [Sat Jan 23 15:05:00 EST 2016]; root of context hierarchy
2016-01-23 [...] s.w.s.m.m.a.RequestMappingHandlerAdapter : Detected ResponseBodyAdvice bean in org.springframework.boot.actuate.autoconfigure.EndpointWebMvcHypermediaManagementContextConfiguration$ActuatorEndpointLinksAdvice
(2)
2016-01-23 [...] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2016-01-23 [...] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2016-01-23 [...] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-01-23 [...] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-01-23 [...] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/docs/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-01-23 [...] .m.m.a.ExceptionHandlerExceptionResolver : Detected ResponseBodyAdvice implementation in org.springframework.boot.actuate.autoconfigure.EndpointWebMvcHypermediaManagementContextConfiguration$ActuatorEndpointLinksAdvice
2016-01-23 [...] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-01-23 [...] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/actuator || /actuator.json],produces=[application/json]}" onto public org.springframework.hateoas.ResourceSupport org.springframework.boot.actuate.endpoint.mvc.HalJsonMvcEndpoint.links()
2016-01-23 [...] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env/{name:.*}],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint.value(java.lang.String)
2016-01-23 [...] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env || /env.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016-01-23 [...] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/metrics/{name:.*}],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint.value(java.lang.String)
2016-01-23 [...] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/metrics || /metrics.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016-01-23 [...] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/info || /info.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016-01-23 [...] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/configprops || /configprops.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016-01-23 [...] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/health || /health.json],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint.invoke(java.security.Principal)
2016-01-23 [...] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/dump || /dump.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016-01-23 [...] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/docs],produces=[text/html]}" onto public java.lang.String org.springframework.boot.actuate.endpoint.mvc.DocsMvcEndpoint.redirect()
2016-01-23 [...] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/docs/],produces=[text/html]}" onto public java.lang.String org.springframework.boot.actuate.endpoint.mvc.DocsMvcEndpoint.browse()
2016-01-23 [...] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/beans || /beans.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016-01-23 [...] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/autoconfig || /autoconfig.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016-01-23 [...] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/mappings || /mappings.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016-01-23 [...] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/trace || /trace.json],methods=[GET],produces=[application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2016-01-23 [...] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2016-01-23 [...] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 0
2016-01-23 [...] application                              : Initializing Spring FrameworkServlet 'dispatcherServlet'
2016-01-23 [...] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2016-01-23 [...] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 27 ms
(1)
2016-01-23 [...] o.eclipse.jetty.server.ServerConnector   : Started ServerConnector@4f67e3df{HTTP/1.1}{0.0.0.0:8100}
2016-01-23 [...] .s.b.c.e.j.JettyEmbeddedServletContainer : Jetty started on port(s) 8100 (http/1.1)
2016-01-23 [...] msvcdojo.MysvcApplication                : Started MysvcApplication in 5.586 seconds (JVM running for 6.144)
1 Listening port of the web service
2 List of available end-points that Actuator provides for you for free.

Play time

Healthcheck

Let’s health-check our brand new service:

$ curl http://localhost:8080/health

The output of this CURL command will look like this:

{
  "status": "UP",
  "diskSpace": {
    "status": "UP",
    "total": 255219200000,
    "free": 70534836224,
    "threshold": 10485760
  }
}

See that the status value is UP. This will be our indication that the service is healthy. This endpoint can be extended and your application can provide more or less information based on the needs of your projects.

Shutdown

Actuator is not enabling a couple of "dangerous" end-points. One of them is the one that the automation infrastructure may use to shutdown the service.

You can send an empty POST request to this endpoint for the service gracefully shutdown itself.

$ curl -d {} http://localhost:8080/shutdown

End-point documentation

The fact that the Actuator-docs package was included in your artifact, your service has received an additional end point that provides embedded documentation for the other actuator-provided end points.

You can access this documentation by navigating to the following url: http://localhost:8080/docs/

Going forward you will not need this dependency so you can remove this line from the build.gradle:

build.gradle
compile('org.springframework.boot:spring-boot-actuator-docs')

Bonus