Start script for Spring Boot applications with sytemd on CentOS 7 and Ubuntu 17.10

Published by Alexander Braun on 27 Jan 2018 - tagged with Spring, Spring Boot, Linux

In this post, we will create a Linux start script for a Spring Boot web application. The example is based on CentOS 7 / Ubuntu 17.10 using systemd. Additionally, we will utilize the Spring Boot Maven plugin to create an executable jar file. This executable jar file will be used in the context of systemd to automatically start the application whenever we restart the Linux server.

Check out the code from GitHub

The code for this example is available in this GitHub repository

Create an executable jar file

To create an executable jar file we can utilize an additional feature of the Spring Boot Maven plugin. Within the build section of our pom.xml file we can usually find the configuration for the Spring Boot Maven plugin. We only have to add a configuration section that specifies to create an executable jar file.

pom.xml

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <executable>true</executable>
        </configuration>
      </plugin>
    </plugins>
  </build>

Build the jar file

Now we can build the jar file with mvn install

[user@centos]$ mvn install

The result of our build process is a new jar file "starter-0.0.1-SNAPSHOT.jar" within the target folder of our project.

Start the web application

Now we can start the web application from the command line using ./target/starter-0.0.1-SNAPSHOT.jar

[user@centos]$ ./target/starter-0.0.1-SNAPSHOT.jar

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

2018-01-27 13:48:55.540  INFO 7685 --- [           main] c.p.code.starter.WebStarterApplication   : Starting WebStarterApplication v0.0.1-SNAPSHOT on asus with PID 7685 (/home/abraun/progressive-code-examples-git/ProgressiveCode/WebStarter/target/starter-0.0.1-SNAPSHOT.jar started by abraun in /home/abraun/progressive-code-examples-git/ProgressiveCode/WebStarter/target)
2018-01-27 13:48:55.542  INFO 7685 --- [           main] c.p.code.starter.WebStarterApplication   : No active profile set, falling back to default profiles: default
2018-01-27 13:48:55.709  INFO 7685 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@30946e09: startup date [Sat Jan 27 13:48:55 PST 2018]; root of context hierarchy
2018-01-27 13:48:56.546  INFO 7685 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8090 (http)
2018-01-27 13:48:56.554  INFO 7685 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2018-01-27 13:48:56.555  INFO 7685 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.23
2018-01-27 13:48:56.641  INFO 7685 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2018-01-27 13:48:56.642  INFO 7685 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 935 ms
2018-01-27 13:48:56.755  INFO 7685 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2018-01-27 13:48:56.755  INFO 7685 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2018-01-27 13:48:56.755  INFO 7685 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2018-01-27 13:48:56.755  INFO 7685 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2018-01-27 13:48:56.756  INFO 7685 --- [ost-startStop-1] .s.DelegatingFilterProxyRegistrationBean : Mapping filter: 'springSecurityFilterChain' to: [/*]
2018-01-27 13:48:56.756  INFO 7685 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2018-01-27 13:48:57.014  INFO 7685 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@8b96fde, org.springframework.security.web.context.SecurityContextPersistenceFilter@4461c7e3, org.springframework.security.web.header.HeaderWriterFilter@429bd883, org.springframework.security.web.csrf.CsrfFilter@69b794e2, org.springframework.security.web.authentication.logout.LogoutFilter@1dd92fe2, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@7f9fcf7f, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@77e4c80f, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@226a82c4, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@2d2e5f00, org.springframework.security.web.session.SessionManagementFilter@279ad2e3, org.springframework.security.web.access.ExceptionTranslationFilter@5a4aa2f2, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@4d826d77]
2018-01-27 13:48:57.186  INFO 7685 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@30946e09: startup date [Sat Jan 27 13:48:55 PST 2018]; root of context hierarchy
2018-01-27 13:48:57.267  INFO 7685 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/],methods=[GET]}" onto public java.lang.String com.progressive.code.starter.controller.StartPageController.getStartPage(org.springframework.ui.Model)
2018-01-27 13:48:57.268  INFO 7685 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/admin],methods=[GET]}" onto public java.lang.String com.progressive.code.starter.admin.controller.AdminController.adminStartPage()
2018-01-27 13:48:57.268  INFO 7685 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/login],methods=[GET]}" onto public java.lang.String com.progressive.code.starter.admin.controller.LoginController.login(org.springframework.ui.Model)
2018-01-27 13:48:57.271  INFO 7685 --- [           main] s.w.s.m.m.a.Rhttps://stegard.net/2016/08/gracefully-killing-a-java-process-managed-by-systemd/equestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2018-01-27 13:48:57.271  INFO 7685 --- [           main] 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)
2018-01-27 13:48:57.301  INFO 7685 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-01-27 13:48:57.301  INFO 7685 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-01-27 13:48:57.342  INFO 7685 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-01-27 13:48:57.710  INFO 7685 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-01-27 13:48:57.754  INFO 7685 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8090 (http)
2018-01-27 13:48:57.759  INFO 7685 --- [           main] c.p.code.starter.WebStarterApplication   : Started WebStarterApplication in 2.449 seconds (JVM running for 2.716)

After adding the new configuration to our pom.xml file and run the maven build, we no longer need to start an application with java -jar appName.jar! The Maven plugin creates an executable jar file and it can be started with appName.jar

Create a systemd service file

To create a systemd service, we have to create service file within the /etc/systemd/system/ directory.

[user@centos]$ sudo vi /etc/systemd/system/web-application.service

The content of this file is shown below:

web-application.service

[Unit]
Description=Spring Boot Web Application
After=networking.service

[Service]
User=webapp
ExecStart=/home/webapp/WebStarter/target/starter-0.0.1-SNAPSHOT.jar
SuccessExitStatus=143
WorkingDirectory=/home/webapp/WebStarter/

[Install]
WantedBy=multi-user.target

The following key properties are used to describe and configure our new service.

  • Description: a simple description of our new service
  • After: specifies the startup sequence, here our service is being started after the network is available. In many cases other dependencies are required, e.g. start the web application after MySQL has been started. In this case, we have to use mysqld.service as a dependency.
  • User: It usually is a good idea to start services with a non-root user. Here we specify the user "webapp" to be used, please replace this with your own user
  • ExecStart: The path to our executable jar file
  • SuccessExitStatus: required to gracefully shutdown a Java-based service, see also here
  • WorkingDirectory: The working directory of our java application. Setting the working directory is quite useful if Spring Boot configuration files - outside of the jar file - have to be read at startup time.
  • WantedBy: specifies the Linux runtime level, usually doesn't have to be changed

Restart daemon

Whenever we create or modify an existing service file it is recommended to execute sudo systemctl daemon-reload to ensure that the systemd daemon is being notified about the changes.

Start the service

After we have created the service file, we can start the service.

[user@centos]$ sudo systemctl start web-application

After a couple of seconds, we can check if the service is up and running.

[user@centos]$ sudo systemctl status web-application

In my case, the output is:

[user@centos]$ sudo systemctl status web-application

  web-application.service
     Loaded: loaded (/etc/systemd/system/web-application.service; bad; vendor preset: enabled)
     Active: active (running) since Sat 2018-01-27 14:32:02 PST; 7s ago
   Main PID: 8663 (starter-0.0.1-S)
      Tasks: 40 (limit: 4915)
     CGroup: /system.slice/web-application.service
             - 8663 /bin/bash /home/webapp/WebStarter/target/starter-0.0.1-SNAPSHOT.jar
             - 8677 /usr/bin/java -Dsun.misc.URLClassPath.disableJarChecking=true -jar /home/webapp/WebStarter/target/starter

Stop the service

We can stop the service using sudo systemctl stop web-application.

[user@centos]$ sudo systemctl stop web-application

Enable the service

The last step is to enable the service sudo systemctl enable web-application. This step ensures that the service is being started every time we restart the Linux server.

[user@centos]$ sudo systemctl enable web-applicationCreated
  symlink /etc/systemd/system/multi-user.target.wants/web-application.service -> /etc/systemd/system/web-application.service.