AJAX with CKEditor in Spring Boot

1. Overview

In this article, we will cover how to use CKEditor with Spring Boot. In this tutorial, we will be importing an XML document with numerous data, program the ability to load a set of data to the CKEditor instance with a GET request, and do a POST request to save the CKEditor’s data.

Technologies we will be using include MongoDB, Thymeleaf, and Spring Batch.

The full source code for this tutorial is available on Github.

2. What is CKEditor?

CKEditor is a browser-based What-You-See-Is-What-You-Get (WYSIWYG) content editor.  CKEditor aims to bring to a web interface common word processor features found in desktop editing applications like Microsoft Word and OpenOffice.

CKEditor has numerous features for end users in regards to the user interface, inserting content, authoring content, and more.

There are different versions of CKEditor, but for this tutorial we are using CKEditor 4. To see a demo, visit: https://ckeditor.com/ckeditor-4/

3. The XML Document

As mentioned, we are uploading an XML document in this application. The XML data will be inserted into the database and used for the rest of the tutorial.

<?xml version="1.0"?>
<Music xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="MUS-1" style="1.1">
<status date="2017-11-07">draft</status>
 <title xmlns:xhtml="http://www.w3.org/1999/xhtml" >Guide to Music I Like - No Specific Genre</title>
  <description xmlns:xhtml="http://www.w3.org/1999/xhtml" >This guide presents a catalog of music that can be found on Spotify. 
    <html:br xmlns:html="http://www.w3.org/1999/xhtml"/>
    <html:br xmlns:html="http://www.w3.org/1999/xhtml"/>
    This is a very small sample of music found on Spotify and is no way to be considered comprehensive.
    </description>
    <songs>
    <song>
    <artist>
    Run the Jewels
    </artist>
    <song-title>Legend Has It</song-title>
    </song>
    <song>
    <artist>
    Kendrick Lamar
    </artist>
    <song-title>ELEMENT.</song-title>
    </song>
    <song>
    <artist>
    Weird Al Yankovic
    </artist>
    <song-title>NOW That's What I Call Polka!</song-title>
    </song>
    <song>
    <artist>
    Eiffel 65
    </artist>
    <song-title>Blue (Da Ba Dee) - DJ Ponte Ice Pop Radio</song-title>
    </song>
    <song>
    <artist>
    YTCracker
    </artist>
    <song-title>Hacker Music</song-title>
    </song>
    <song>
    <artist>
    MAN WITH A MISSION
    </artist>
    <song-title>
    Raise Your Flag
    </song-title>
    </song>
    <song>
    <artist>
    GZA, Method Man
    </artist>
    <song-title>
    Shadowboxin'
    </song-title>
    </song>
    </songs>
</Music>

4. Model

For the above XML code, we can model a Song like this:

public class SongModel {
    @Id
    private String id;
    @Indexed
    private String artist;
    @Indexed
    private String songTitle;
    @Indexed
    private Boolean updated;
    
    public Boolean getUpdated() {
        return updated;
    }
    public void setUpdated(Boolean updated) {
        this.updated = updated;
    }
    public String getArtist() {
        return artist;
    }
    public void setArtist(String artist) {
        this.artist = artist;
    }
    public String getSongTitle() {
        return songTitle;
    }
    public void setSongTitle(String songTitle) {
        this.songTitle = songTitle;
    }
    
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    
    @JsonCreator
    public SongModel(
        @JsonProperty("artist") String artist,
        @JsonProperty("song-title") String songTitle){
        this.artist = artist;
        this.songTitle = songTitle;
    }
 
    @Override
    public String toString() {
      return "Person [id=" + id + ", artist=" + artist + ", song-title=" + songTitle + "]";
    }

}

For our application, we will be differentiating between an unmodified song and a song that has been modified in CKEditor with a separate Model and Repository.

Let’s now define what an Updated Song is:

public class UpdatedSong {
    
    @Id
    private String id;
    @Indexed
    private String artist;
    @Indexed
    private String songTitle;
    @Indexed
    private String html;
    @Indexed
    private String sid;
    
    public String getSid() {
        return sid;
    }
    public void setSid(String sid) {
        this.sid = sid;
    }
    
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getArtist() {
        return artist;
    }
    public void setArtist(String artist) {
        this.artist = artist;
    }
    public String getSongTitle() {
        return songTitle;
    }
    public void setSongTitle(String songTitle) {
        this.songTitle = songTitle;
    }
    public String getHtml() {
        return html;
    }
    public void setHtml(String html) {
        this.html = html;
    }

}

5. File Upload and Processing

As this article’s focus is on CKEditor and AJAX, we aren’t going to go into detail on the file upload and processing with Spring Batch. We have reviewed this process in depth in these prior posts, however:

6. Setting Data to CKEditor – GET Request

It is our goal to retrieve the data for an individual song and display that data in CKEditor. There are two issues to tackle: retrieving the data for an individual song and displaying it in CKEditor.

6.1 Client Side Code

In view.html, we use a table in Thymeleaf to iterate through each Song in the Song repository. To be able to retrieve the data from the server for a specific song, we pass in the Song’s id to a function.

Here’s the snippet of code that is responsible for calling the function that retrieves the data from the server and subsequently sets the data to the CKEditor instance:

<table class="table datatable">
<thead>
<tr>
<th>Artist</th>
<th>Song Title</th>
<th>Load</th>
</tr>
</thead>
<tbody>
<tr th:each="songList : ${songList}">
<td th:text="${songList.artist}">Text ...</td>
<td th:text="${songList.songTitle}">Text ...</td>
<td><button th:onclick="|getSong('${songList.id}')|" id="button" class="btn btn-primary btn-condensed">
<i class="glyphicon glyphicon-folder-open"></i>
</button></td>
</tr>
</tbody>
</table>

As we can see, the id of Song is essential for us to be able to retrieve the data.

In the getSong function, we use a deferred promise to ensure that data is set after the GET request:

		function getSong(song) {
			$.ajax({
				url : "/api/show/?sid=" + song,
				type : 'GET',
				dataType : 'text'
			}).then(function(data) {
				var length = data.length-2;
				var datacut = data.slice(9,length);
				CKEDITOR.instances.content.setData(datacut);

			});

			$("#form").attr("action", "/api/save/?sid=" + song);

		};

6.2 Server Side Code

getSong accepts a parameter named sid, which stands for Song id. sid is also a path variable in the @GetMapping. We treat sid as a String because this is the id of the Song from MongoDB.

We check if the Song has been modified and if so we retrieve the associated UpdatedSong entity. If not, we treat the Song differently. Ultimately, we return a simple POJO with a String for data named ResponseModel, however:

    @GetMapping(value={"/show/","/show/{sid}"})
    public ResponseEntity<?> getSong(@RequestParam String sid, Model model){
        ResponseModel response = new ResponseModel();
        System.out.println("SID :::::" + sid);
        ArrayList<String> musicText = new ArrayList<String>();
        if(sid!=null){
            String sidString = sid;
            SongModel songModel = songDAO.findOne(sidString);
            System.out.println("get status of boolean during get ::::::" + songModel.getUpdated());
            if(songModel.getUpdated()==false ){
                
                musicText.add(songModel.getArtist());
                musicText.add(songModel.getSongTitle());
                String filterText = format.changeJsonToHTML(musicText);
                response.setData(filterText);
                
            } else if(songModel.getUpdated()==true){
                UpdatedSong updated = updatedDAO.findBysid(sidString);
                String text = updated.getHtml();
                System.out.println("getting the updated text ::::::::" + text);
                response.setData(text);
            }
            
        }

        model.addAttribute("response", response);
        
        return ResponseEntity.ok(response);
    }

ResponseModel is a very simple POJO as mentioned:

public class ResponseModel {
    private String data;
    
    public ResponseModel(){
            
    }
    
    public ResponseModel(String data){
            this.data = data;
    }

    public String getData() {
            return data;
    }

    public void setData(String data) {
            this.data = data;
    }
}

 

7. Saving CKEditor Data – POST Request

POSTing the data isn’t much of a challenge; however, ensuring that the data is handled appropriately can be.

7.1 Client Side Code

As the CKEditor instance is a textarea within a form, we can trigger a function on a form submit:

$(document)
.ready(
function() {

// SUBMIT FORM
$("#form").submit(function(event) {
// Prevent the form from submitting via the browser.
event.preventDefault();
ajaxPost();
});

ajaxPost() retrieves the current data in the CKEditor and sets it to the variable formData:

function ajaxPost() {

// PREPARE FORM DATA
var formData = CKEDITOR.instances.content
.getData();

// DO POST
$
.ajax({
type : "POST",
contentType : "text/html",
url : $("#form").attr("action"),
data : formData,
dataType : 'text',
success : function(result) {

$("#postResultDiv")
.html(
"

"
+ "Post Successfully! "
+ "

");

console.log(result);
},
error : function(e) {
alert("Error!")
console.log("ERROR: ", e);
}
});

}

})

It is important to note:

  • contentType is “text/html”
  • dataType is “text”

Having the incorrect contentType or dataType can lead to errors or malformed data.

7.2 Server Side Code

We stated in our contentType for the POST request that the mediatype is “text/html”.  We need to specify in our mapping that this will be consumed. Therefore, we add consumes = MediaType.TEXT_HTML_VALUE with our @PostMapping.

Areas for us to note include:

  • @RequestBody String body is responsible for setting the variable body to the content of our request
  • We once again return ResponseModel, the simple POJO described earlier that contains our data
  • We treat a previously modified SongModel different than one that has not been modified before

Also, like the GET request, the sid allows us to deal with the correct Song:

 @PostMapping(value={"/save/","/save/[sid]"}, consumes = MediaType.TEXT_HTML_VALUE)
    public @ResponseBody ResponseModel saveSong( @RequestBody String body, @RequestParam String sid){
        ResponseModel response = new ResponseModel();
        response.setData(body);
        SongModel oldSong = songDAO.findOne(sid);
        String songTitle = oldSong.getSongTitle();
        String artistName = oldSong.getArtist();
        if(oldSong.getUpdated() == false){
            UpdatedSong updatedSong = new UpdatedSong();
            updatedSong.setArtist(artistName);
            updatedSong.setSongTitle(songTitle);
            updatedSong.setHtml(body);
            updatedSong.setSid(sid);
            oldSong.setUpdated(true);
            songDAO.save(oldSong);
            updatedDAO.insert(updatedSong);
            System.out.println("get status of boolean during post :::::" + oldSong.getUpdated());
        }else{
            UpdatedSong currentSong = updatedDAO.findBysid(sid);
            currentSong.setHtml(body);
            updatedDAO.save(currentSong);
        }        
        
        return response;
    }

8. Demo

We visit localhost:8080:

home page for example ckeditor spring boot application

We upload the provided music-example.xml file:

Screen shown after upload for example ckeditor application

We click “Load” for a song:

loaded content into ckeditor

We add content and click “Save”:

we add content and click save ckeditor

If you return to the saved content, you may see “\n”for line breaks. For the time being, discussing this is out of the scope for the tutorial.

9. Conclusion

In this tutorial, we covered how to load  data using a GET request with the object’s id, set the data to the CKEditor instance, and save the CKEditor’s data back to the database with a POST request. There’s extra code, such as using two different entities for the data (an original and a modified version), that isn’t necessary, but hopefully is instructive.

The full code can be found on Github.

Building Spring Boot RESTful Service + Spring Boot Actuator

Overview

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 system.
The 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, 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

package com.michaelcgood.model;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;

import com.fasterxml.jackson.annotation.JsonIgnore;

@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.
package com.michaelcgood.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

import com.fasterxml.jackson.annotation.JsonIgnore;

@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

package com.michaelcgood.dao;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

import com.michaelcgood.model.Account;

public interface AccountRepository extends JpaRepository<Account,Long> {

	Optional<Account> findByUsername(String username);
}

UsernamesRepository.java

package com.michaelcgood.dao;

import java.util.Collection;

import org.springframework.data.jpa.repository.JpaRepository;

import com.michaelcgood.model.Usernames;

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

package com.michaelcgood.controller;

import java.net.URI;
import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import com.michaelcgood.dao.AccountRepository;
import com.michaelcgood.dao.UsernamesRepository;
import com.michaelcgood.model.Usernames;

@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.

package com.michaelcgood.controller;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

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

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

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.

package com.michaelcgood;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import com.michaelcgood.dao.AccountRepository;
import com.michaelcgood.dao.UsernamesRepository;
import com.michaelcgood.model.Account;
import com.michaelcgood.model.Usernames;

import java.util.Arrays;

@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