Spring Boot Tutorial
1. Introduction
If you always wanted to work with a Web Framework which allows you to jump-start into API development without the hassle of setting up Web Servers, collecting all the wired dependencies, installing various tools, you’ve been blessed with an excellent framework, Spring Boot. The prime motto of Spring Boot is convention over configuration.
In this lesson, we will study how easy it is to set up a Spring Boot project and make some RESTful services which interact with a database in under 20 minutes! Exactly, that’s how easy it is with Spring Boot. Spring Boot allows us to make production-grade Spring-based applications that you can “just run”. The best part of a Spring boot application is that it needs very little configuration.
2. Making the Spring Boot Project with Maven
We will be using one of the many Maven archetypes to create a sample project for our example. To create the project execute the following command in a directory that you will use as workspace:
mvn archetype:generate -DgroupId=com.javacodegeeks.example -DartifactId=JCG-SpringBoot-example -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
If you are running maven for the first time, it will take a few seconds to accomplish the generate command because maven has to download all the required plugins and artifacts in order to make the generation task.
Once you have created the project, feel free to open it in your favourite IDE. Next step is to add appropriate Maven Dependencies to the project. We will work with four dependencies in our project:
spring-boot-starter-web
: This dependency marks this project as a Web project and it adds dependencies in order to create controllers and make Web-related classes.spring-boot-starter-data-jpa
: This dependency provides the project with the JPA support where we can perform many Database related operationsh2
: H2 is an in-memory database which we will use to demonstrate DB operations in this lessonspring-boot-starter-test
: This dependency collects all test related JARs into the project like JUnit and Mockito.
Here is the pom.xml
file with the appropriate dependencies:
pom.xml
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.10.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-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
To package the code into a JAR, we will make use of spring-boot-maven-plugin
which is an excellent tool to manage packaging of the application itself into a JAR or a WAR. Here is how we can add into our dependency file:
pom.xml
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
Find the latest Maven dependencies on Maven Central.
Finally, to understand all the JARs which are added to the project when we added this dependency, we can run a simple Maven command which allows us to see a complete Dependency Tree for a project when we add some dependencies to it. Here is a command which we can use:
Check Dependency Tree
mvn dependency:tree
When we run this command, it will show us the following Dependency Tree:
Dependency Tree
[INFO] -------------------- [INFO] Building JCG-SpringBoot-example 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-dependency-plugin:2.10:tree (default-cli) @ JCG-SpringBoot-example --- [INFO] com.javacodegeeks.example:JCG-SpringBoot-example:jar:1.0-SNAPSHOT [INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:1.5.10.RELEASE:compile [INFO] | +- org.springframework.boot:spring-boot-starter:jar:1.5.10.RELEASE:compile [INFO] | | +- org.springframework.boot:spring-boot:jar:1.5.10.RELEASE:compile [INFO] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:1.5.10.RELEASE:compile [INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:1.5.10.RELEASE:compile [INFO] | | | +- ch.qos.logback:logback-classic:jar:1.1.11:compile [INFO] | | | | \- ch.qos.logback:logback-core:jar:1.1.11:compile [INFO] | | | +- org.slf4j:jul-to-slf4j:jar:1.7.25:compile [INFO] | | | \- org.slf4j:log4j-over-slf4j:jar:1.7.25:compile [INFO] | | \- org.yaml:snakeyaml:jar:1.17:runtime [INFO] | +- org.springframework.boot:spring-boot-starter-aop:jar:1.5.10.RELEASE:compile [INFO] | | +- org.springframework:spring-aop:jar:4.3.14.RELEASE:compile [INFO] | | \- org.aspectj:aspectjweaver:jar:1.8.13:compile [INFO] | +- org.springframework.boot:spring-boot-starter-jdbc:jar:1.5.10.RELEASE:compile [INFO] | | +- org.apache.tomcat:tomcat-jdbc:jar:8.5.27:compile [INFO] | | | \- org.apache.tomcat:tomcat-juli:jar:8.5.27:compile [INFO] | | \- org.springframework:spring-jdbc:jar:4.3.14.RELEASE:compile [INFO] | +- org.hibernate:hibernate-core:jar:5.0.12.Final:compile [INFO] | | +- org.jboss.logging:jboss-logging:jar:3.3.1.Final:compile [INFO] | | +- org.hibernate.javax.persistence:hibernate-jpa-2.1-api:jar:1.0.0.Final:compile [INFO] | | +- org.javassist:javassist:jar:3.21.0-GA:compile [INFO] | | +- antlr:antlr:jar:2.7.7:compile [INFO] | | +- org.jboss:jandex:jar:2.0.0.Final:compile [INFO] | | +- dom4j:dom4j:jar:1.6.1:compile [INFO] | | \- org.hibernate.common:hibernate-commons-annotations:jar:5.0.1.Final:compile [INFO] | +- org.hibernate:hibernate-entitymanager:jar:5.0.12.Final:compile [INFO] | +- javax.transaction:javax.transaction-api:jar:1.2:compile [INFO] | +- org.springframework.data:spring-data-jpa:jar:1.11.10.RELEASE:compile [INFO] | | +- org.springframework.data:spring-data-commons:jar:1.13.10.RELEASE:compile [INFO] | | +- org.springframework:spring-orm:jar:4.3.14.RELEASE:compile [INFO] | | +- org.springframework:spring-context:jar:4.3.14.RELEASE:compile [INFO] | | +- org.springframework:spring-tx:jar:4.3.14.RELEASE:compile [INFO] | | +- org.springframework:spring-beans:jar:4.3.14.RELEASE:compile [INFO] | | +- org.slf4j:slf4j-api:jar:1.7.25:compile [INFO] | | \- org.slf4j:jcl-over-slf4j:jar:1.7.25:compile [INFO] | \- org.springframework:spring-aspects:jar:4.3.14.RELEASE:compile [INFO] +- org.springframework.boot:spring-boot-starter-web:jar:1.5.10.RELEASE:compile [INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:1.5.10.RELEASE:compile [INFO] | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:8.5.27:compile [INFO] | | | \- org.apache.tomcat:tomcat-annotations-api:jar:8.5.27:compile [INFO] | | +- org.apache.tomcat.embed:tomcat-embed-el:jar:8.5.27:compile [INFO] | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:8.5.27:compile [INFO] | +- org.hibernate:hibernate-validator:jar:5.3.6.Final:compile [INFO] | | +- javax.validation:validation-api:jar:1.1.0.Final:compile [INFO] | | \- com.fasterxml:classmate:jar:1.3.4:compile [INFO] | +- com.fasterxml.jackson.core:jackson-databind:jar:2.8.10:compile [INFO] | | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.8.0:compile [INFO] | | \- com.fasterxml.jackson.core:jackson-core:jar:2.8.10:compile [INFO] | +- org.springframework:spring-web:jar:4.3.14.RELEASE:compile [INFO] | \- org.springframework:spring-webmvc:jar:4.3.14.RELEASE:compile [INFO] | \- org.springframework:spring-expression:jar:4.3.14.RELEASE:compile [INFO] +- com.h2database:h2:jar:1.4.196:runtime [INFO] \- org.springframework.boot:spring-boot-starter-test:jar:1.5.10.RELEASE:test [INFO] +- org.springframework.boot:spring-boot-test:jar:1.5.10.RELEASE:test [INFO] +- org.springframework.boot:spring-boot-test-autoconfigure:jar:1.5.10.RELEASE:test [INFO] +- com.jayway.jsonpath:json-path:jar:2.2.0:test [INFO] | \- net.minidev:json-smart:jar:2.2.1:test [INFO] | \- net.minidev:accessors-smart:jar:1.1:test [INFO] | \- org.ow2.asm:asm:jar:5.0.3:test [INFO] +- junit:junit:jar:4.12:test [INFO] +- org.assertj:assertj-core:jar:2.6.0:test [INFO] +- org.mockito:mockito-core:jar:1.10.19:test [INFO] | \- org.objenesis:objenesis:jar:2.1:test [INFO] +- org.hamcrest:hamcrest-core:jar:1.3:test [INFO] +- org.hamcrest:hamcrest-library:jar:1.3:test [INFO] +- org.skyscreamer:jsonassert:jar:1.4.0:test [INFO] | \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test [INFO] +- org.springframework:spring-core:jar:4.3.14.RELEASE:compile [INFO] \- org.springframework:spring-test:jar:4.3.14.RELEASE:test [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------
Noticed something? So many dependencies were added by just adding four dependencies to the project. Spring Boot collects all related dependencies itself and leave nothing for us in that matter. The biggest advantage is that all these dependencies are guranteed to be copatible with each other.
3. Gradle equivalent for the build file
Although Maven is an excellent build-system, if you prefer Gradle, here is the Gradle equivalent for the pom.xml
build file:
build.gradle
buildscript { ext { springBootVersion = '1.5.10.RELEASE' } repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' group = 'com.javacodegeeks.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile('org.springframework.boot:spring-boot-starter-data-jpa') compile('org.springframework.boot:spring-boot-starter-web') runtime('com.h2database:h2') testCompile('org.springframework.boot:spring-boot-starter-test') }
We have exhibited exactly the same dependencies and versions to provide an exact equivalent.
4. Project Structure
Before we move on and start working on the code for the project, let’s present here the projet structure we will have once we’re finished adding all the code to the project:
We have divided the project into multiple packages so that the principle of separation of concern is followed and code remains modular.
5. Defining an Entity
Let’s look at how this can be done:
Person.java
package com.javacodegeeks.example.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class Person { @Id @GeneratedValue private Long id; private String name; private int age; //standard getters and setters @Override public String toString() { return String.format("Person{id=%d, name='%s', age=%d}", id, name, age); } }
We omitted standard getters and setters for brevity but they are necessary to be made as Jackson uses them during Serialization and Deserialization of an Object.
The @Entity
annotation marks this POJO as an object which will be managed by the Spring Data APIs and its fields will be treated as table columns (unless marked transient).
Finally, we added a custom implementation for the toString()
method so that we can print-related data when we test our application.
6. Defining JPA Repository to access H2 Database
JPA provides us with a very simple way of defining a JPA Repository interface.
Before getting to know how to define a JPA Repository, we need to remember that each JPA interface is only made to interact with a single Entity of Database Table when JPA-related functionality is leveraged. We will understand this deeply if we have a look at the interface definition:
PersonRepository.java
package com.javacodegeeks.example.repository; import com.javacodegeeks.example.model.Person; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface PersonRepository extends JpaRepository<Person, Long> { }
Although above interface definition is empty, we still have some points which we need to understand:
@Repository
annotation marks this interface as a Spring Bean which is initialised on application startup. With this annotation, Spring takes care of managing exception database interaction throws gracefully- We used
Person
as a parameter to signify that this JPA interface will manage thePerson
Entity - Finally, we also passed the data type
Long
as a parameter. This signifies that thePerson
Entity contains a unique identifier which is for the typeLong
.
7. Making a RESTful Controller
A RESTful Controller where we expose the application’s data to a client. We will make use of several HTTP verbs like GET, POST, PUT and DELETE to support features associated with them.
To start, let’s define a PersonController
class which is marked as @RestController
. The @RestController
annotation signals the Spring Container that any exceptions which are raised in this class are Ok to be passed on to the client itself. This is a different behaviour in comparison to a Repository bean.
PersonController.java
package com.javacodegeeks.example.controller; import com.javacodegeeks.example.model.Person; import com.javacodegeeks.example.repository.PersonRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController public class PersonController { private final Logger LOG = LoggerFactory.getLogger(getClass().getName()); private final PersonRepository personRepository; @Autowired public PersonController(PersonRepository personRepository) { this.personRepository = personRepository; } ... }
Let’s provide some power to this Controller by adding specific APIs which perform actual operations.
7.1 Inserting data with POST
We will start by adding an API through which we can add data to the H2 Database. As this method accepts a Person
Object in its @RequestBody
, the JSON for Person object must be passed to the API in the request.
PersonController.java
@RequestMapping(value = "", method = RequestMethod.POST) public Person createPerson(@RequestBody Person person) { LOG.info("Saving Person: {}.", person); personRepository.save(person); LOG.info("Person saved: {}.", person); return person; }
We made use of the Personrepository
bean to access a method which is pre-defined in the JpaRepository
interface we defined earlier to deal with Person
Entity.
In the coming section, we will try this API in a REST client, Postman.
7.2 Constructing a GET API
Now that we have an API to insert data into the Database, we can ow construct an API to Get the Person Object with its ID. Here, the personId
is passed as a PathVariable
:
PersonController.java
@RequestMapping(value = "/{personId}", method = RequestMethod.GET) public Person getPerson(@PathVariable Long personId) { Person person = personRepository.findOne(personId); LOG.info("Got person from DB: {}.", person); return person; }
To get all data which is present in the database, we will make yet another GET API to get all data from the database:
PersonController.java
@RequestMapping(value = "/all", method = RequestMethod.GET) public List<Person> getAllPerson() { List<Person> persons = personRepository.findAll(); LOG.info("Getting all Data: {}.", persons); return persons; }
It just makes use of the JPA Repository’s findAll
method to get all data of Person
in DB and collect it into a List.
7.3 Updating data with PUT
We will now allow a client to update the existing data from the database. For this, we make use of save
method again. When save
method sees that the JSON object is populated with the id
field, it first finds the Object with that ID and then it updates the fields provided.
PersonController.java
@RequestMapping(value = "", method = RequestMethod.PUT) public Person editPerson(@RequestBody Person person) { LOG.info("Updating Person: {}.", person); personRepository.save(person); LOG.info("Person updated: {}.", person); return person; }
7.4 Deleting data with DELETE
We will now have a final operation of deleting the data when a person’s ID is passed as a PathVariable
:
PersonController.java
@RequestMapping(value = "/{personId}", method = RequestMethod.DELETE) public void deletePerson(@PathVariable Long personId) { LOG.info("Deleting Person with ID {}.", personId); personRepository.delete(personId); LOG.info("Person deleted."); }
8. Including a Request Interceptor
Although this is not strictly needed, we will include a Request Interceptor in this example as well. A Request Interceptor allows us to do something with the request object before it reaches the Controller. Once the Controller is done with the request and returns the response, that response object again passes through the Request Interceptor. Let’s define the Request Interceptor here:
JCGRequestInterceptor.java
package com.javacodegeeks.example.interceptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component public class JCGRequestInterceptor extends HandlerInterceptorAdapter { private final Logger LOG = LoggerFactory.getLogger(getClass().getName()); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { LOG.info("Incoming request."); return super.preHandle(request, response, handler); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { LOG.info("Outgoing request."); super.postHandle(request, response, handler, modelAndView); } }
9. Running the Project with Maven
Before we run the project, we need to define a main class for the project as well. Here is the main class definition with the Request Interceptor bean:
Running the Project
package com.javacodegeeks.example; import com.javacodegeeks.example.interceptor.JCGRequestInterceptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @SpringBootApplication public class BootApp { public static void main(String[] args) { SpringApplication.run(BootApp.class, args); } @Bean public JCGRequestInterceptor requestInterceptor() { return new JCGRequestInterceptor(); } }
Now that it is done, we can run our project. Running the application is easy with maven, just use the following command:
Running the Project
mvn spring-boot:run
Now that the project is running, we can use Postman tool to access APIs and see if they are working as expected.
10. Accessing APIs in Postman
We will start by inserting some data into the H2 Database wth the POST
API we made:
Now, we can get this Person object with ID JPA assigned it, i.e. 1:
We can also try the Get All data API to see if this Object is returned or not:
Let’s update one of the field of the object we created:
Finally, we try to delete the data by passing the ID in the URL as a PathVariable
:
11. Including and Running Unit Tests
Any Spring application is incomplete without at least one unit-test case. In this example, we will include a single unit-test case which is production ready. Here it goes:
ControllerTests.java
package com.javacodegeeks.example; import com.javacodegeeks.example.controller.PersonController; import com.javacodegeeks.example.repository.PersonRepository; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringJUnit4ClassRunner.class) public class ControllerTest { private MockMvc mockMvc; @InjectMocks private PersonController controller; @Mock private PersonRepository repository; @Before public void setUp() { MockitoAnnotations.initMocks(this); controller = new PersonController(repository); mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); } @Test public void getAllPerson_Pass() throws Exception { mockMvc.perform( get("/all")) .andExpect(status().is2xxSuccessful()); } }
We did several things in above sample test-case. Let’s understand these one at a time:
- I marked this class a
@RunWith(SpringJUnit4ClassRunner.class)
annotation which provides the test case with a runner. - When we use Mockito, we need to enable its annotations. This is done by the
initMocks(...)
method call. - We used the
mockMvc
object’sget
nethod to check the status code returned by the/all
GET API and compared it to any2XX
status code.
When we ran this test-case in IntelliJ, we see the following output:
Seeing a test-case with green on it is an excellent feeling, isn’t it?
12. Using Spring Boot CLI
Spring Boot Command Line Interface is a software which we can use to run and test Spring Boot applications from the command line. Spring Boot CLI internally makes use of Spring Boot starters and AutoConfiguration components to collect the required dependencies and run the application.
To start using CLI, quickest way is to download the ZIP and change into the bin directory and check the command as shown:
To use Spring CLI from anywhere, add this JAR to your PATH.
Now, let us quickly show how powerful Spring Boot CLI is. CLI can be used to execute nad run single Groovy-based scripts without providing any dependencies. We will make a single file and name it “HelloWorld.groovy”. Do take note of the file extension here as it is necessary that the file is of type groovy only. In the file, we will put simple code fragment:
HelloWorld.groovy
@RestController class HelloWorld { @RequestMapping("/") String hello() { "Hello World!" } }
Now, move to the folder where you made this script and run the following command:
HelloWorld.groovy
spring run HelloWorld.groovy
This command will run the mentioned Grovvy script on the default port 8080. Let’s try visiting this port now:
Wasn’t it easy? Note that there were no dependencies, no configuration and no import statements involved when we wrote the Groovy script. This is because this responsibility is taken by Spring Boot Core Components, Groovy Compiler (groovyc
) and Groovy Grape (Groovy’s JAR Dependency Manager).
13. Conclusion
In this lesson, we looked at how easy and quick it is to construct a production-grade API with Spring Boot. We managed to make some fully-functional APIs which talked to the database and running a unit-test case as well.
We tried the APIs in a production-grade RESTful client, Postman and saw our APIs responding to calls as expected. The H2 Database we used in this lesson is very easy to be replaced with a real database like MySQL, MongoDB or any other database.
14. Download the Source Code
In this example, we looked at how we can get started with a basic Spring Boot project.
You can download the full source code of this example here: JCG-SpringBoot-Example
Hello!
But why not UI for crud?
Thanx,
Alex.
Hello Alexander,
Actually, I am working on a new lesson which shows UI for CRUD operations with Angular 4 and Spring Boot and it’s coming soon! I will keep you posted for the lesson :)
Cheers,
Shubham A.
It’s will be amazing if You link these articles both.
There are many tutorials about REST CRUD without UI (most of them are worst with no abstraction – there are couple of boilerplate for each entity), but UI for REST CRUD I can’t find that are good.
Thanx, I’ll wait eagerly :)
This is a userful tutorial for Spring Boot. But can you please explain the use of the JCGRequestInterceptor class? For example, at run-time I would expect to see the message “Incoming request.” in IntelliJ before the messages hardcoded into method createPerson in the PersonController class, but there is no trace of them.