Spring Boot for Dummies: Part 1.1 [The Basics]

Yash Patel
14 min readNov 23, 2023

--

In this series, I intend to familiarize you with core concepts used in Spring Boot and the most widely used technologies to make robust and scalable applications. We will also explore some key System Design topics and how they can drive our choices.

#What is this series about?

In this series, we will learn to develop an application from the ground up and will add things as we see fit to fulfill our design goals. Alongside discussing important programming concepts, we will build an application using Spring Boot, let’s call the application Simply-Todo. As the name suggests it’s a Todo list application. I have chosen this application because most people understand what a TODO list is and can easily follow the business logic. But don’t be deceived that it’s a simple application and there is not much to do here. We are going to build an advanced TODO application with multiple features.

#We will learn in this series!

  • Spring Basics: How to create and setup a spring-boot application, learn about the inner workings of the spring-boot application, Spring’s three-tier architecture, what is Spring IOC and how it works, error handling, web API, spring profiles, actuators etc.
  • Spring Data: Spring JPA (Java Persistent API), data validations, connecting to databases, both Relational and Non-Relational [NoSQL].
  • Spring Security: How to Secure your application, with spring security, with the three most popular ways for authentication Using JWT, O-Auth, and Form post for Authentication, and we will build custom @Annotations for Authorization to cover complex cases.
  • Integrating third party services: Adding Payment system to our app using STRIPE. And integrating with other third-party applications.
  • Scaling Application: Using Caching, Message Queues, Kafka, CRON Jobs, Docker, Kubernetes and other such technologies.
  • Further Improving performance by Database Sharding, and horizontal scaling while designing architectures.
  • CI/CD setup of application code to auto-deploy the application on git push, and other UI actions.

Note: I have added docker-compose files under container folder on my git repository to host the necessary service in a container, you just need to install docker on your computer for that. You can download docker desktop for your OS here. We will also learn about docker in the later parts of this series.

There is much more to this, that I can’t include here. So let get started!

#Why Spring?

Spring is by far the most popular Java framework for building production-ready web applications, Micro Services, APIs, etc. Spring Framework and Spring Boot offer some of the most crucial functionality out of the box which is apt for building large-scale enterprise applications and getting you started in no time. Besides, there is a large community of developers, which makes it very easy to find solutions to most common bottlenecks. Spring’s base mantra is to focus less on configuration and more on business logic for a faster development pace.

#Spring vs Spring-Boot: What’s the difference?

Spring boot simply said is the extension of the Spring Framework which bootstraps (fastens) our development as we only need to focus on the business logic of the application without having to worry about the configuration part.

Let’s take a scenario where we need to add a Bean (it is an object managed by spring Inversion of Control IOC, for now just think of it as an object managed by spring) to the spring application we need to configure it like this.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="/WEB-INF/red5-web.properties" />
</bean>

<bean id="idDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName"><value>${db.driver}</value></property>
<property name="url"><value>${db.url}</value></property>
<property name="username"><value>${db.username}</value></property>
<property name="password"><value>${db.password}</value></property>
<property name="poolPreparedStatements"><value>true</value></property>
<property name="maxActive"><value>10</value></property>
<property name="maxIdle"><value>10</value></property>
</bean>

</beans>

With Spring boot, it is as easy as adding this to the application properties file.

spring.dbProductService.driverClassName = com.mysql.jdbc.Driver
spring.dbProductService.url = Dburl
spring.dbProductService.username = root
spring.dbProductService.password = root
spring.dbProductService.max-active = 10
spring.dbProductService.max-idle = 10

Furthermore, Spring Boot provides starter dependency, which is like a bundle of all the essential dependencies related to the particular task.

For example, if we are creating a web application, we want all dependencies that are typically used to develop a web application. Well, Spring-Boot provides such starter dependencies, in this case, spring starter web. But there are many more such as test starters, data JPA starter, security starter, etc.

For example, spring-starter-web has internal dependencies as:

  • Spring-boot-starter => spring-beans, spring-core
  • Starter-Json => Jackson ….
  • … (and more)

By importing Spring-starter-web we have got all the dependencies that are used for a web application.

Read: If you want to create your own custom starter, you can do so like this.

#What is an API?

An API stands for Application Programming Interface. It is a set of functions and procedures that allow different software applications to communicate with each other. In other words, it is a way for two or more computer programs to interact with each other. It is a set of definitions and protocols for building and integrating application software.

Well, that sounds very bookish, it is basically all the functions in your code that you choose to expose in a certain way.

For example, for a library, the API is the set of functions exposed to the end user, in this case, the functions exposed like connect, disconnect, etc. The user does not have to concern themselves with internal logic, just call the function/API exposed and they are good to go.

class ConnectionUtil{
public Boolean connect();
public Boolean disconnect();
public void onSuccess();
public void onFailure():
}

REST API

A RESTful API (Representational State Transfer) is an architectural style for designing networked applications. It’s an approach to building web services that are:

  • Stateless: Each request from a client to a server must contain all the information necessary to understand and fulfill the request. This means that the server doesn’t rely on any information from previous requests.
  • Uniform Interface: This is a key constraint of REST. It means that the API should have a consistent and predictable way of interacting with resources. This typically involves the use of standard HTTP methods (GET, POST, PUT, DELETE) and standard conventions for resource naming.
  • Client-Server: The client and server are separate entities that communicate over a network. This separation allows them to evolve independently and allows for scalability. The client is responsible for the user interface and user experience, while the server is responsible for processing requests, managing resources, and handling business logic.
  • Resource-Based: Resources are the key abstractions in a RESTful API. A resource can be any piece of information that can be named, such as a document, image, or service. Each resource is identified by a unique URI (Uniform Resource Identifier).

It’s worth noting that many APIs that are called “RESTful” may not strictly adhere to all these principles, but they generally follow the core concepts to varying degrees.

A simple Example

A web API exposes internal functions, definitions and protocols over web or network. The below example image shows an API endpoint to receive the ball, change color to blue and save a Ball in some kind of Database. (Don’t stress it too much, you will have an intuitive understanding just after the first two parts).

This is an oversimplification of what a Web API is, but it gives a basic idea for someone unfamiliar with the topic.

# The three-tier Architecture (Controller, Service, Repository/Persistence)

Before we start to write any code, we need to understand how a typical spring web application is organized.

In three three-tier architecture applications, we have the following layers.

  1. Controller Layer — Interacts with external applications and the web e.g. accepting requests, redirecting pages, returning responses, etc.
  2. Service Layer — This is where the business logic is written for the application, e.g. Request validation, maintaining context, transforming and processing data, etc.
  3. Persistence Layer — This layer is responsible for storing the processed results in some database/datastore, or it is the layer that interacts with databases to save and restore application data.

This architecture is widely accepted as a solution to organize codebases into three separate layers with distinctive responsibilities. Each layer could only be called by the layer below it.

You will see this in action when we create a project and start organizing our code according to these three layers.

#Setting up a Spring-Boot project?

There are 2 popular ways to get started with spring.

  1. start.spring.io you can go here and download the project and open it in any IDE of your choice.
  2. You can add Spring extension to your IDE and do the same from there. I prefer IntelliJ Idea you can download the IDE here.

#Demo — Adding CRUD (Create, Read, Update, Delete) API logic.

Let’s create a simple Web API with a Controller and Service layer. In order to store the data, we will use a simple data structure (HashMap ⇒ key-value pair) in our case that will store the id of our Todo object as a key and the object itself as a value.

We want to keep our controllers and services and all other logically different components in a different subpackage; this helps organize the code better.

The package with the Controller will have all the controllers inside it. Similarly, the package with service will have all the services and hence the business logic. The entity typically contains all the objects that we persist in databases.

Note: We have added all these packages inside the main package com.simplytodo this is important because the annotation @Componentscan under @Springbootapplication only scans the current package and its sub-packages. If you want the application to scan other packages you can do it like this.

@SpringBootApplication
@ComponentScan(basePackages = {"com.example.package1", "com.example.package2"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

Step 1: Add the TodoTask.java under entity as this is something that we would eventually want to store in a database.

public class TodoTask {
private static int idCounter = 0;

private int id = idCounter++;
private String title;
private String description;
// Its an enum that we defined
private TodoTaskStatus status = TodoTaskStatus.NOT_STARTED;
private Date dueDate;
private Date createdAt = Date.from(java.time.Instant.now());
}

Step 2: Create a service that can perform create, read, update, and delete operations with our internal data structure to store TodoTask (i.e. HashMap).

@Service
public class TodoService {

// Our internal structure to store tasks
private static HashMap<Integer, TodoTask> todoTaskStore = new HashMap<Integer, TodoTask>();

public TodoTask createOrUpdate(TodoTask todoTask) {
return todoTaskStore.put(todoTask.getId(), todoTask);
}
public TodoTask getTask(int id) {
return todoTaskStore.get(id);
}
public void delete(int id) {
if(todoTaskStore.containsKey(id))
todoTaskStore.remove(id);
}
public List<TodoTask> getAllTasks() {
return todoTaskStore.values().stream().toList();
}
}

🔭 observe @Service this tells Spring to create a bean for this class managed by Spring and chance can be injected into other classes.

Step 3: Create a controller for exposing the functionality in service through the web.

@RestController("/api")
public class TodoController {

@Autowired
private TodoService todoService; // injected our service

@GetMapping("/task/:id")
public TodoTask getTask(@PathVariable int id){
return todoService.getTask(id);
}

@GetMapping("/tasks")
public List<?> getAllTasks(){
return todoService.getAllTasks();
}

@DeleteMapping("/task/:id")
public void deleteTask(@PathVariable int id){
todoService.delete(id);
}

@PostMapping("/task")
public TodoTask createTask(@RequestBody TodoTask todoTask){
return todoService.createOrUpdate(todoTask);
}
}

🔭 observe @Restcontroller it consists of two annotations @Controller + @ResponseBody . The main difference between Spring @RestController and @Controller annotations is that the former combines the functionality of the latter and @ResponseBody. This means that a @RestController class does not need to annotate its methods with @ResponseBody to return data in JSON or XML format. A @Controller class, on the other hand, needs to use @ResponseBody for each method that returns data or use a view resolver to return a view.

Let’s run the project. First, create the run/debug configuration.

You can now run the project by clicking the run button on the top left. If everything runs fine you should see the following output.

Observe: The Tomcat server has started on port 8080. If you want to change the port that spring application starts with add the following line to the application.properties server.port=8081 you can add your own port.

Let’s test our API by calling it through POSTMAN. It is an API platform for building and using APIs.

Create Request (HTTP POST)

we can send an Http Post request to http://localhost:8080/api/task with the body as shown, you should get a 200 OK response.

Get Request (HTTP GET)

Now when we do Http Get. It should return us the Task created in the above step.

Update request (HTTP PATCH)

Observer we updated the description, if you do HTTP Get on tasks, you should see the updated description.

Delete Request (HTTP DELETE)

If you are working with Git, please use the commit with the section name + updated. For working code.

Now, if you try to do HTTP Get, it should return an empty response as we created one task and deleted one.

# Error Handling

Error handling is an important aspect of any application development process. In Java, error handling is typically done using exception-handling mechanisms. Exceptions are objects that represent abnormal conditions that can occur during the execution of a program. When an exceptional condition occurs, an exception is thrown, which can then be caught and handled by the appropriate code.

In Java, there are two types of exceptions: checked exceptions and unchecked exceptions. Checked exceptions are exceptions that must be declared in the method signature or handled using a try-catch block. Unchecked exceptions, on the other hand, do not need to be declared or caught explicitly.

We have the following choices to handle exceptions.

Let’s use the try-catch block. The try block contains the code that may throw an exception, and the catch block is used to catch and handle the exception, if you don’t know what kinds of exception will be thrown, you can handle all types of Exceptions using the Exception class, as it is the base class for all Exceptions in Java. You can have multiple catch blocks to handle different types of exceptions. The exceptions will be caught in the order they are declared, for example, if ExceptionType1 is thrown (although it is also an Exception technically) it will enter the first catch block catching ExceptionType1. If the exception thrown in the try block does not match ExceptionType1 or ExceptionType2, then the third block with just Exception will definitely catch it.

Here’s an example of how exception handling works in Java:

try {
// code that may throw an exception
// ...
} catch (ExceptionType1 e1) {
// handle exception of type ExceptionType1
// ...
} catch (ExceptionType2 e2) {
// handle exception of type ExceptionType2
// ...
} catch (Exception e){
// handles all exceptions
}
finally {
// code that will always execute, regardless of whether an exception occurred or not
// ...
}

In addition to the try-catch block, you can also use the finally block to specify code that will always execute, regardless of whether an exception occurred or not.

If you don’t want to handle the exception where it’s thrown, but rather let the caller handle the logic, then we can add throws Exception at the end of the method from where it's thrown.

class Demo{
public method1()throws Exception1, Exception2{
// code throws exception1 or exception2
}

public method2(){
try{
this.method1()
}catch(Exception1 e1){
// handle or rethrow
}catch(Exception e){
// We have not added Exception2 here but this block will cath it.
// to handle any other or all exceptions
}
}
}

public static void main(String args[]){
Demo d = new Demo();
d.method2()
}

Note: You can rethrow the exception from catch and also add throws Exception to the method to signify that this method can throw these exceptions.

Handling Exceptions from controllers. With this newfound knowledge let’s add a way to handle all exceptions that can be thrown from our controllers. This will standardize the response the user sees and gives us more power as to what information we need to expose to the user.

Let’s add an error handler class TodoErrorController to the application. Add this under subpackage errors, in case of our application com.simplytodo.errors

Here’s an example of how you can create a global error handler class:

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class TodoErrorController{

@ExceptionHandler(Exception.class)
public ResponseEntity<TodoErrorResponse> handleException(Exception e) {
TodoErrorResponse errorResponse = new TodoErrorResponse();
errorResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
errorResponse.setMessage(e.getMessage());

return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}

// Optionally, add more @ExceptionHandler methods for specific exceptions
// example replace CustomException.class with your exception type
@ExceptionHandler(CustomException.class)
public ResponseEntity<TodoErrorResponse> handleCustomException(CustomException e){

TodoErrorResponse errorResponse = new TodoErrorResponse();
errorResponse.setStatus(HttpStatus.<your error status>);
errorResponse.setMessage(e.getMessage());
return new ResponseEntity<>(errorResponse, HttpStatus.<your error status>);
}


}

// we also create an appropriate CustomErrorResponse class as follow
public class TodoErrorResponse {
private HttpStatus status;
private String message;
// getters and setters
}

In this example, @ControllerAdvice makes this class catch exceptions across the whole application. The @ExceptionHandler annotation is used to specify which exceptions this method should handle.

Now, whenever an unhandled exception occurs in your application, it will be caught by the handleException method in TodoErrorController, and it will return a standardized error response.

The error handling code setup: Best practices.

A string error handling and reporting setup goes a long way when we develop complex applications. In general, we create our own exception class let’s say TodoException that extends from Exception and also create an Enum like TodoErrorStatus.

We then modify and add values to TodoErrorStatus as our application evolves and handles code that can throw errors in our code. This makes it highly customizable and makes sure we only communicate essential information with the end-user.

The class TodoException and TodoErrorStatus will look like this.

package com.simplytodo.errors;

public class TodoException extends Exception{
private TodoErrorStatus status;
public TodoException(TodoErrorStatus status, String message) {
super(message);
this.status = status;
}

public TodoErrorStatus getStatus() {
return status;
}
}

// Added a few common error status enums
public enum TodoErrorStatus{
INTERNAL_SERVER_ERROR,
NOT_FOUND,
BAD_REQUEST,
UNAUTHORIZED,
FORBIDDEN,
METHOD_NOT_ALLOWED,
CONFLICT,
UNSUPPORTED_MEDIA_TYPE,
TOO_MANY_REQUESTS
}

We will further see how we will leverage this setup to handle mode advance scenarios (especially with Authentication and Authorization).

If you have any questions, please feel free to discuss in comments, I will try to reply for as soon as possible. Thank you. Happy Coding :)

Here is the link to Part 1.2

--

--

Yash Patel

Software Developer. Extremely Curious | Often Wrong | Always Learning.