is a web framework built on top of the framework . It is designed for easier use and quicker implementation. It does so by configuring the application and its environment as automatically as possible. As a newcomer, I can say that it makes the framework really easy to get into. Spring Boot Spring My learning led me to read most of the , which is well written and gives you a lot of insights into the internal behavior of Spring Boot. This documentation gives a lot of details, so this article aims to take the counter approach and pinpoint the concepts you will need to implement an API using Spring Boot. I will complement each section with a set of links to related documentation, may you want to dig further. reference documentation As a side note, this document will be using version 2.4.2 of the framework, on a Java project using Gradle as the build system. However, the information remains applicable to any compatible language and build system. This article will cover the following aspects of creating an API with Spring Boot: Bootstrap the project Create REST endpoints Handle errors Connect to a persistence layer Paginate the results Test the application Package the Application Bootstrap the project This part may be the easiest, as Spring Boot is providing a package generator at . We can select all required modules and retrieve an archived project with the build system, dependencies, and main application class. https://start.spring.io/ Outside of this generator, to declare a RESTful API, our project should define the Spring Boot dependency. The dependencies are a set of ready-to-use features packaged by Spring Boot. starter web starter plugins { id version } dependencies { implementation } 'org.springframework.boot' '2.4.2' 'org.springframework.boot:spring-boot-starter-web' The application’s main method should be contained in any class, on which we should apply the annotation . This annotation is responsible for a lot of automatic configurations, namely the components injection and web server startup. @SpringBootApplication { { SpringApplication.run(MyApplication.class, args); } } @SpringBootApplication public class MyApplication public static void main (String[] args) Starting the server is as simple as using the embedded command . The server will start, but we don’t have any endpoint to serve at the moment. ./gradlew bootRun Documentation links: @SpringBootApplication List of starter dependencies Create a REST endpoint To create a controller, we simply have to annotate any class with . We can then configure any method inside this controller as an endpoint using . @RestController @RequestMapping help us configuring the endpoint by providing an URL, the HTTP verb, the expected data type, and more. It can be applied both on a class and a method, the configurations applied on the class will be inherited by the methods underneath and the path concatenated. @RequestMapping To control our endpoint status codes we will return a , holding both the response message and . ResponseEntity HttpStatus (value = , consumes = MediaType.ALL_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) { (value = , method = RequestMethod.GET) ResponseEntity<Map<String, String>> index() { HashMap<String, String> output = HashMap<>(); output.put( , ); ResponseEntity<>(output, HttpStatus.OK); } } @RestController @RequestMapping "/hello" public class HelloWorldController @RequestMapping "/world" public new "message" "Hello World!" return new The will be automatically transformed to an HTTP response, using the as response code and transforming the message to a JSON object. On top of transforming to JSON objects, Spring Boot configure to map all public attributes or getters of any class to a JSON object. ResponseEntity HttpStatus Maps Jackson HTTP/1.1 200 {"Hello":"World"} $ curl -i "localhost:8080/hello/world" Documentation links: @RestController and @RequestMapping @RequestMapping API doc Customize Json Serialization Advanced endpoint configuration Now that we have a controller, we may want to define dynamic HTTP endpoints. To do so, the main annotations to keep in mind are: : Defines a body structure through a java Class. @RequestBody : Defines a variable subpart of the endpoint URL. @PathVariable : Defines a query parameter. @RequestParam The controller below showcases the three annotations with two endpoints, each returning a custom “Hello World” depending on the query. (value = , consumes = MediaType.ALL_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) { (value = , method = RequestMethod.POST) ResponseEntity<Map<String, String>> greetFromBody( HelloBody helloBody) { HashMap<String, String> output = HashMap<>(); output.put( , + helloBody.getName()); ResponseEntity<>(output, HttpStatus.OK); } (value = , method = RequestMethod.GET) ResponseEntity<Map<String, String>> greet( String name, (required = , defaultValue = ) amount_exclamation) { HashMap<String, String> output = HashMap<>(); StringBuilder b = StringBuilder( ); b.append(name); ( i = ; i < amount_exclamation; i++) { b.append( ); } output.put( , b.toString()); ResponseEntity<>(output, HttpStatus.OK); } } { String name; { } { .name; } } @RestController @RequestMapping "/hello" public class HelloWorldController // The behavior is not representative of a typical POST request // and only here as a matter of example. @RequestMapping "" public @RequestBody new "message" "Hello " return new @RequestMapping "/{name}" public @PathVariable @RequestParam false "0" int new new "Hello " for int 0 "!" "message" return new class HelloBody public HelloBody () // Used by Jackson String public getName () return this The endpoints defined above can be used as follow: HTTP/1.1 200 {"message":"Hello jack!!!!"} HTTP/1.1 200 {"message":"Hello Bob"} $ curl -i "localhost:8080/hello/jack?amount_exclamation=4" # -d automatically creates a POST request. $ curl -i -d -H "localhost:8080/hello" '{"name": "Bob"}' "Content-Type: application/json" Documentation links: @RequestBody @PathVariable @RequestParam Handle errors By default, Spring Boot will return the HTTP code 200 for any successful request, 404 if the endpoint is not registered, and 500 for any error. We already saw that using enables us to override this behavior for successful requests, but we still need to handle error codes more finely. ResponseEntity To do so, we will define custom API exceptions that will be automatically transformed into HTTP codes. This transformation is done by a class extending and annotated with . In this class, we can define methods to handle exceptions using the annotations and . ResponseEntityExceptionHandler @ControllerAdvice @ExceptionHandler @ResponseStatus { (ApiException.class) (HttpStatus.BAD_REQUEST) { } (NotFoundException.class) (HttpStatus.NOT_FOUND) { } } { } { } @ControllerAdvice public class MyApplicationControllerAdvice extends ResponseEntityExceptionHandler @ExceptionHandler @ResponseStatus public void handleBadRequest () @ExceptionHandler @ResponseStatus public void handleNotFound () public class ApiException extends Exception public class NotFoundException extends ApiException After defining the in your project, any exception thrown by your controllers will be parsed and transformed to the bound . ControllerAdvice ResponseStatus (value = ) { (value = , method = RequestMethod.GET) ResponseEntity<Map<String, String>> notFound() NotFoundException { NotFoundException(); } (value = , method = RequestMethod.GET) ResponseEntity<Map<String, String>> badRequest() ApiException { ApiException(); } (value = , method = RequestMethod.GET) ResponseEntity<Map<String, String>> ise() Exception { Exception(); } } @RestController @RequestMapping "/exception" public class ExceptionController @RequestMapping "/404" public throws throw new @RequestMapping "/400" public throws throw new @RequestMapping "/500" public throws throw new HTTP/1.1 500 HTTP/1.1 404 HTTP/1.1 400 $ curl -i "localhost:8080/exception/500" $ curl -i "localhost:8080/exception/404" $ curl -i "localhost:8080/exception/400" Our exception handling is very simple and does not return any payload, but it is possible to implement exception parsing in the methods of ResponseEntityExceptionHandler. Documentation links: ResponseEntityExceptionHandler @ControllerAdvice @ExceptionHandler @ResponseStatus Connect to a persistence layer Configuration To use a database, we will need the (JPA) package and the implementation of any persistence layer. The former will install interface APIs, while the latter will provide the implementations and drivers. Java Persistence API To pinpoint the minimal changes required to switch between two distinct databases, we will show the integration with both and at the same time. First, let’s declare our dependencies: PostgreSQL H2 dependencies { implementation implementation implementation } 'org.springframework.boot:spring-boot-starter-data-jpa' // Dependencies to your used dbms 'org.postgresql:postgresql:42.2.1' 'com.h2database:h2:1.4.200' The second step is to configure the accesses in . The property file is the first and the last time we will have to worry about our persistence configuration. In this file, the 3 lines commented out are the only part to change to switch from PostgreSQL to H2. application.properties = = = = = = = spring.datasource.username user spring.datasource.password password spring.datasource.generate-unique-name true # Automatically create & update the database schema from code. spring.jpa.hibernate.ddl-auto update #spring.datasource.url=jdbc:h2:mem:database_name spring.datasource.url jdbc:postgresql://localhost:5432/database_name #spring.datasource.driver-class-name=org.h2.Driver spring.datasource.driver-class-name org.postgresql.Driver #spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.database-platform org.hibernate.dialect.PostgreSQL10Dialect Documentation links: Database configuration Available properties Define a model Defining a model is as simple as using annotations defined on . These annotations are available through the package which is available through the JPA dependency. JSR-317 javax.persistence, For instance, the code below creates a entity. Our entity identifier is the field which will be automatically initialized and increased on each new saved entity in the database thanks to the annotation . Delivery id, @GeneratedValue Note: All attributes publicly available will be set into the JSON representation of the entity in the API responses. (name = ) { (strategy = GenerationType.AUTO) id; (nullable = ) (EnumType.STRING) DeliveryState state; (nullable = ) String location; { } { .state = state; .location = location; } { id; } { state; } { .state = state; } { location; } { .location = location; } } DeliveryState { PENDING, DELIVERING, WAITING_AT_ARRIVAL, RETURNING, RETURNED, PICKED_UP; } @Entity @Table "delivery" public class Delivery @Id @GeneratedValue long @Column false @NotNull @Enumerated @Column false @NotNull public Delivery () // Used by Jackson2 public Delivery (@NotNull DeliveryState state, @NotNull String location) this this public long getId () return DeliveryState public getState () return public void setState (DeliveryState state) this String public getLocation () return public void setLocation (String location) this enum To ensure consistency of our data class, we applied validations from , these validations can be enforced on endpoints as we will see during the next section. The constraints are contained in the package , available through the dependency . @NotNull JSR-303 javax.validation.constraints spring-boot-starter-validation dependencies { implementation } 'org.springframework.boot:spring-boot-starter-validation' Documentation links: Entity Declaration javax.persistence API documentation (@Entity, @Column, @Enumerate, …) @GeneratedValue javax.validation.constraints API documentation (@NotNull) Expose the model To interact with our models, we have to define a , for instance, a . Doing so is as easy as extending the class with an empty class. Spring Boot will automatically implement functions to interact with the entity. Repository CrudRepository { } @Repository public < , > interface DeliveryRepository extends CrudRepository Delivery Long We annotate this component to make it available for dependency injection. Then we can inject and use the repository in any class, for example directly in a controller. Using will automatically retrieve the declared above @Repository @Autowired @Repository . Note: @Repository and @Service behave exactly as the main injection annotation @Component , it simply enables to mark a semantic difference. (value = , consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) { DeliveryRepository deliveryRepository; { .deliveryRepository = deliveryRepository; } (value = , method = RequestMethod.POST) { { delivery = deliveryRepository.save(delivery); } (Exception e) { ApiException(); } ResponseEntity<>(delivery, HttpStatus.OK); } (value = , method = RequestMethod.GET) { Optional<Delivery> delivery = deliveryRepository.findById(id); (delivery.isEmpty()) { NotFoundException(); } ResponseEntity<>(delivery.get(), HttpStatus.OK); } } @RestController @RequestMapping "/delivery" public class DeliveryController private final @Autowired public DeliveryController (DeliveryRepository deliveryRepository) this @RequestMapping "" ResponseEntity<Delivery> ApiException public post (@Valid @RequestBody Delivery delivery) throws try catch throw new return new @RequestMapping "/{id}" ResponseEntity<Delivery> ApiException public get (@PathVariable id) long throws if throw new return new We used the annotation to ensure that our constraints defined above are met on the sent body. @Valid Delivery -X POST -d '{"state": "PENDING"}' HTTP/1.1 400 HTTP/1.1 404 -X POST -d '{"state": "PENDING", "location":"Budapest"}' HTTP/1.1 200 {"id":1,"state":"PENDING","location":"Budapest"} HTTP/1.1 200 {"id":1,"state":"PENDING","location":"Budapest"} $ curl -i -H \ "localhost:8080/delivery" 'Content-Type: application/json' $ curl -i -H "localhost:8080/delivery/1" 'Content-Type: application/json' $ curl -i -H \ "localhost:8080/delivery" 'Content-Type: application/json' $ curl -i -H 130 ↵ "localhost:8080/delivery/1" 'Content-Type: application/json' Note: H2 is an in-memory database so the data will be wiped out at each server restart. Documentation Links: CrudRepository API Documentation Spring Component Declaration javax.validation API documentation (@Valid) Paginate the results This section illustrates how well Spring Boot integrates some classic features of a web API. To paginate the access to our previous entity we simply have to change the repository’s extended class from to . Delivery, CrudRepository PagingAndSortingRepository { } @Repository public < , > interface DeliveryRepository extends PagingAndSortingRepository Delivery Long This repository implementation provides a new method returning a . The class configures the page and page size to return. findAll(Pageable) Page Pageable (value = , consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) { DeliveryRepository deliveryRepository; { .deliveryRepository = deliveryRepository; } (value = , method = RequestMethod.GET) ResponseEntity<Page<Delivery>> index( (required = , defaultValue = ) page) { Pageable pageable = PageRequest.of(page, ); ResponseEntity<>(deliveryRepository.findAll(pageable), HttpStatus.OK); } } @RestController @RequestMapping "/delivery" public class DeliveryController private final @Autowired public DeliveryController (DeliveryRepository deliveryRepository) this @RequestMapping "" public @RequestParam false "0" int 50 return new The endpoint will then serve the whole object’s data upon request. Page { "content": [ { "id": 1, "state": "PENDING", "location": "Budapest" } ], "pageable": { "sort": { "sorted": false, "unsorted": true, "empty": true }, "offset": 0, "pageNumber": 0, "pageSize": 50, "paged": true, "unpaged": false }, "totalPages": 1, "totalElements": 1, "last": true, "first": true, "size": 50, "number": 0, "sort": { "sorted": false, "unsorted": true, "empty": true }, "numberOfElements": 1, "empty": false } $ curl -H | jq 4 ↵ "localhost:8080/delivery" 'Content-Type: application/json' Documentation links: PagingAndSortingRepository API Documentation PageRequest API Documentation Page API Documentation Test the application Spring Boot provides every tool to easily test controllers with a set of APIs and . Mostly, will enable us to send requests and assert response content without having to worry about technicalities. mocks MockMvc As an example, we are testing the endpoint from the section above. One of these tests is successfully creating a entity, and the second one simulates an error coming from the database. POST Delivery To avoid relying on a physical instance of a persistence layer, we injected our DeliveryRepository instance using , which creates and injects a mock of our component. @MockBean { MockMvc mvc; DeliveryRepository deliveryRepository; { ObjectMapper mapper = ObjectMapper(); Map<String, String> delivery = getValidDelivery(); String body = mapper.writeValueAsString(delivery); MockHttpServletRequestBuilder accept = MockMvcRequestBuilders.post( ) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content(body); mvc.perform(accept).andExpect(status().isOk()); } { ObjectMapper mapper = ObjectMapper(); Map<String, String> delivery = getValidDelivery(); String body = mapper.writeValueAsString(delivery); Mockito.when(deliveryRepository.save(Mockito.any())).thenThrow( RuntimeException()); MockHttpServletRequestBuilder accept = MockMvcRequestBuilders.post( ) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .content(body); mvc.perform(accept).andExpect(status().is4xxClientError()); } { Map<String, String> delivery = HashMap<>(); delivery.put( , ); delivery.put( , ); delivery; } } @SpringBootTest @AutoConfigureMockMvc class DeliveryControllerTest @Autowired private @MockBean @Test Exception void testPostDeliveryOk () throws new "/delivery" @Test Exception void testPostPersistIssue () throws new new "/delivery" Map<String, String> private getValidDelivery () new "state" "PENDING" "location" "Rome" return Documentation links: @SpringBootTest @AutoConfiguredMockMvc @MockBean MockMvc api Documentation Package the application Spring boot also eases the application packaging either as a standalone jar or a docker image. To create a ready to run , execute . fat jar ./gradlew bootJar To build a , execute . docker image ./gradlew bootBuildImage Note that docker does not like uppercase characters in the image name, but we can easily customize the image name and version. tasks.named( ) { imageName = } // Only use lowercase on docker image name "bootBuildImage" "${rootProject.name.toLowerCase()}:${version}" Documentation links: Create an application fat jar Configure Docker Image Conclusion Spring Boot can be used with a handful of annotations and will manage most of the configuration for you. However, most of the configuration can be overridden to provide your own behavior if necessary. This makes it a good framework to design proof of concepts while keeping room for optimization if the project grows specific needs. If you want to know more about the framework, I can’t stress enough the quality of the , which gives really good details. Reference Documentation If you want to play around with some code, you can find all those concepts on an example delivery API . on GitHub Previously published at https://aveuiller.github.io/spring_boot_apprentice_cookbook.html Disclaimer: The author provides this code and software “AS IS”, without warranty of any kind, express or implied, including but not limited to fitness for a particular purpose and non-infringement. In no event shall the author be liable for any claim, damages or other liability in connection with the software or code provided here