Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Building a Spring Boot RESTful Service + Spring Boot Actuator

DZone's Guide to

Building a Spring Boot RESTful Service + Spring Boot Actuator

In this tutorial, you'll see the structure and learn how to create a Spring Boot RESTful web service that can be monitored with Actuator.

· Integration Zone ·
Free Resource

Ready for feedback: How would you use Capital One’s Virtual Card Numbers API?

What Is REST?

REST (REpresentational State Transfer) is the architectural style the web is built on, and has become a standard software design pattern used for web applications. The term Representational State Transfer was first used by Roy Fielding, the originator of REST and one of the principal authors of HTTP specification, in his doctoral dissertation.

There are many good references on REST, including:

This tutorial is based on Building Rest Services with Spring, and the beginning of the tutorial has a good overview of REST as well.

What Is Spring Boot Actuator?

Spring Boot Actuator is a sub-project of Spring Boot. It adds several production-grade services to your application with minimal effort on your part.

Definition of Actuator

An actuator is a component responsible for moving or controlling a systemThe term actuator is not limited to Spring Boot; however, that is our focus here.

After Actuator is configured in your Spring Boot application, it allows you to interact and monitor your application by invoking different technology agnostic endpoints exposed by Spring Boot Actuator such as application health, beans, loggers, mappings, and trace. More are listed in this Spring doc.

0 - Spring Boot RESTful Web Service With Actuator Example Application

We will build an example RESTful web application with Spring Boot and Actuator.

The application will be a "username tracker." In this application, a person has one account and their account may have many usernames.

View and Download the code from GitHub.

1 - Project Structure

As usual, we have a normal Maven project structure.project structure of spring boot actuator rest example

2 - Project Dependencies

Besides typical Spring Boot dependencies, we are including HSQLDB for our embedded database and spring-boot-starter-actuator for all the Actuator dependencies:

<?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.michaelcgood</groupId>
    <artifactId>michaelcgood-springbootactuator</artifactId>
    <version>0.0.1</version>
    <packaging>jar</packaging>

    <name>Spring-Boot-Actuator-Example</name>
    <description>Michael C  Good - Spring Boot Actuator Example</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.6.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-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
         <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
    </dependencies>

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


</project>


3 - Run the Empty Application

Although we haven't written any code, we will run the Spring Boot application.

Go to your terminal and follow along with the commands:

mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080
{"timestamp":1505235455245,"status":404,"error":"Not Found","message":"No message available","path":"/"}


We haven't written any code yet, so instead of a default container-generated HTML error response, Actuator produces you a JSON response from its /error endpoint:

mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/health
{"status":"UP"}


The Actuator /health endpoint will let you know if your application is up.

4 - Model

Now let's define the fields of the models for our username tracker application.

  • As mentioned, a person has one account and may have many usernames. So we map Set with a @OneToMany annotation.
  • A username model will have a password and a username, of course.
  • Our model will need an ID and we make it autogenerated.
  • We make a class construction to define an account may be made with a username and a password. Because of this custom constructor, we also need to make a default one with no parameters.

Account.java

@Entity
public class Account {

 public Set < Usernames > getUsernames() {
  return usernames;
 }

 public void setUsernames(Set < Usernames > usernames) {
  this.usernames = usernames;
 }

 public Long getId() {
  return id;
 }

 public void setId(Long id) {
  this.id = id;
 }

 public String getPassword() {
  return password;
 }

 public void setPassword(String password) {
  this.password = password;
 }

 public String getUsername() {
  return username;
 }

 public void setUsername(String username) {
  this.username = username;
 }

 @OneToMany(mappedBy = "account")
 private Set < Usernames > usernames = new HashSet < > ();

 @Id
 @GeneratedValue
 private Long id;

 @JsonIgnore
 public String password;
 public String username;

 public Account(String name, String password) {
  this.username = name;
  this.password = password;
 }

 Account() {

 }

}


Usernames.java

  • As there is one account to many usernames, the reverse is true as well: there many usernames to one account. Therefore, we map Account with @ManyToOne annotation.
  • To track a username we need: the url and the username.
  • We once again define an autogenerated ID.
  • We define a custom class constructor that requires account, URL, and username parameter. Once again we need to define a default constructor method to avoid an error being thrown.
@Entity
public class Usernames {

 @JsonIgnore
 @ManyToOne
 private Account account;

 public Account getAccount() {
  return account;
 }

 public void setAccount(Account account) {
  this.account = account;
 }

 public Long getId() {
  return id;
 }

 public void setId(Long id) {
  this.id = id;
 }

 public String getUrl() {
  return url;
 }

 public void setUrl(String url) {
  this.url = url;
 }

 public String getUsername() {
  return username;
 }

 public void setUsername(String username) {
  this.username = username;
 }

 @Id
 @GeneratedValue
 private Long id;

 public String url;
 public String username;

 Usernames() {

 }

 public Usernames(Account account, String url, String username) {
  this.url = url;
  this.account = account;
  this.username = username;
 }



}


5 - Repository

We create a repository for both models and create search functions using derived queries.

AccountRepository.java

public interface AccountRepository extends JpaRepository < Account, Long > {

 Optional < Account > findByUsername(String username);
}


UsernamesRepository.java

public interface UsernamesRepository extends JpaRepository < Usernames, Long > {

 Collection < Usernames > findByAccountUsername(String username);

}


6 - Controller

In the controller, we define all the mapping that we will be using for our RESTful web service.

  • We annotate our controller with @RestController rather than @Controller. As stated in the javadoc, it is "a convenience annotation that is itself annotated with @Controller and @ResponseBody."
  • We declare the variables for our UsernamesRepository and AccountRepository and make them final because we only want the value to be assigned once. We annotate them as @Autowired over the UsernamesRestController class constructor.
  • {userId} and {usernamesId} are path variables. That means these values are provided in a URL. This will be shown in our demo.
  • The Controller methods return POJOs (Plain Old Java Objects). Spring Boot automatically wires HttpMessageConverter to convert these generic objects to JSON.

UsernamesRestController.java

@RestController
@RequestMapping("/{userId}/usernames")
public class UsernamesRestController {

 private final UsernamesRepository usernamesRepository;
 private final AccountRepository accountRepository;

 @Autowired
 UsernamesRestController(UsernamesRepository usernamesRepository, AccountRepository accountRepository) {
  this.usernamesRepository = usernamesRepository;
  this.accountRepository = accountRepository;
 }

 @GetMapping
 Collection < Usernames > readUsernames(@PathVariable String userId) {
  this.validateUser(userId);
  return this.usernamesRepository.findByAccountUsername(userId);
 }

 @PostMapping
 ResponseEntity < ? > add(@PathVariable String userId, @RequestBody Usernames input) {
  this.validateUser(userId);

  return this.accountRepository.findByUsername(userId)
   .map(account -> {
    Usernames result = usernamesRepository.save(new Usernames(account, input.url, input.username));

    URI url = ServletUriComponentsBuilder
    .fromCurrentRequest().path("/{id}")
    .buildAndExpand(result.getId()).toUri();

    return ResponseEntity.created(url).build();
   })
   .orElse(ResponseEntity.noContent().build());
 }

 @GetMapping(value = "{usernamesId}")
 Usernames readUsername(@PathVariable String userId, @PathVariable Long usernameId) {
  this.validateUser(userId);
  return this.usernamesRepository.findOne(usernameId);
 }

 private void validateUser(String userId) {
  this.accountRepository.findByUsername(userId).orElseThrow(
   () -> new UserNotFoundException(userId));
 }


}


UserNotFoundException.java

Here we define the custom exception we used in our Controller class to explain a user could not be found:

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {

 /**
  * 
  */
 private static final long serialVersionUID = 7537022054146700535 L;

 public UserNotFoundException(String userId) {
  super("Sorry, we could not find user '" + userId + "'.");
 }


}


7 - @SpringBootApplication

We use the CommandLineRunner to create accounts and insert usernames. Every account will have two usernames:

@SpringBootApplication
public class SpringBootActuatorExampleApplication {

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

 @Bean
 CommandLineRunner init(AccountRepository accountRepository,
  UsernamesRepository usernamesRepository) {
  return (evt) -> Arrays.asList(
    "ricksanchez,mortysmith,bethsmith,jerrysmith,summersmith,birdperson,squanchy,picklerick".split(","))
   .forEach(
    a -> {
     Account account = accountRepository.save(new Account(a,
      "password"));
     usernamesRepository.save(new Usernames(account,
      "http://example.com/login", a + "1"));
     usernamesRepository.save(new Usernames(account,
      "http://example2.com/login", "the_" + a));
    });
 }
}


8 - Configuration

It is stated in the Spring documentation:

By default all sensitive HTTP endpoints are secured such that only users that have an ACTUATOR role may access them. Security is enforced using the standard HttpServletRequest.isUserInRole method.

We haven't set up any security and user roles, as this is just an example. So, for ease of demonstration, I will be disabling the security requirement. Otherwise, we will get an "unauthorized" error as of right now, like the one shown below:

{"timestamp":1505321635068,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource.","path":"/beans"}


application.properties

Add this to your application.properties to disable the need for authentication:

management.security.enabled=false


9 - Demo

To retrieve the responses from the server, you could either visit the URL in your browser or use curl. For my demo, I am using curl.

REST Queries for Data in Repositories

Query for usernames belonging to account jerrysmith:

mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/jerrysmith/usernames
[{"id":7,"url":"http://example.com/login","username":"jerrysmith1"},{"id":8,"url":"http://example2.com/login","username":"the_jerrysmith"}]


Query for usernames belonging to account picklerick:

mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/picklerick/usernames
[{"id":15,"url":"http://example.com/login","username":"picklerick1"},{"id":16,"url":"http://example2.com/login","username":"the_picklerick"}]


Actuator Queries

The response to this query is truncated because it is really, really long.

Beans


mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/beans
[{"context":"application","parent":null,"beans":[{"bean":"springBootActuatorExampleApplication","aliases":[],"scope":"singleton","type":"com.michaelcgood.SpringBootActuatorExampleApplication$$EnhancerBySpringCGLIB$$509f4984","resource":"null","dependencies":[]},{"bean":"org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory","aliases":[],"scope":"singleton","type":"org.springframework.core.type.classreading.CachingMetadataReaderFactory","resource":"null","dependencies":[]},{"bean":"usernamesRestController","aliases":[],"scope":"singleton","type":"com.michaelcgood.controller.UsernamesRestController","resource":"file [/Users/mike/javaSTS/Spring-Boot-Actuator-Example/target/classes/com/michaelcgood/controller/UsernamesRestController.class]","dependencies":["usernamesRepository","accountRepository"]},{"bean":"init","aliases":[],"scope":"singleton","type":"com.michaelcgood.SpringBootActuatorExampleApplication$$Lambda$11/889398176","resource":"com.michaelcgood.SpringBootActuatorExampleApplication",
[...]


Metrics

mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/metrics
{"mem":350557,"mem.free":208275,"processors":4,"instance.uptime":213550,"uptime":240641,"systemload.average":1.6552734375,"heap.committed":277504,"heap.init":131072,"heap.used":69228,"heap":1864192,"nonheap.committed":74624,"nonheap.init":2496,"nonheap.used":73062,"nonheap":0,"threads.peak":27,"threads.daemon":23,"threads.totalStarted":30,"threads":25,"classes":9791,"classes.loaded":9791,"classes.unloaded":0,"gc.ps_scavenge.count":11,"gc.ps_scavenge.time":139,"gc.ps_marksweep.count":2,"gc.ps_marksweep.time":148,"httpsessions.max":-1,"httpsessions.active":0,"datasource.primary.active":0,"datasource.primary.usage":0.0,"gauge.response.beans":14.0,"gauge.response.info":13.0,"counter.status.200.beans":2,"counter.status.200.info":1}


9 - Conclusion

Congratulations, you have created a RESTful Web Service that can be monitored with Actuator. REST is really the most organic way for different clients to communicate because it works due to HTTP.

The source code is on GitHub.

Learn how Capital One’s Virtual Card Numbers can benefit digital merchants and consumers.

Topics:
spring boot ,rest api ,rest ,actuator ,integration ,web services

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}