Skip to content

Spring Boot Notes

@Component annotation marks a class as a Spring-managed bean. It does not automatically start any logic inside the class unless you explicitly trigger it.

Spring detects a @Component-annotated class during component scanning and adds it to the application context. To make a @Component run at startup you have many options:

  1. Implement CommandLineRunner interface and override run() method,
  2. Annotate a method with @PostConstruct annotation,
  3. Annotate a method with @Scheduled annotation (for periodic tasks).

Please Note: @Scheduled annotation over a @Component class method requires the main class to be annotated with @EnableScheduling.

InitTask.java
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class InitTask {
@PostConstruct
public void init() {
log.info(">>> initialized automatically after bean creation.");
}
}
StartupTask.java
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class StartupTask implements CommandLineRunner {
@Override
public void run(String... args) {
log.info(">>> running at startup");
}
}
SchedulerTask.java
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class SchedulerTask {
@Scheduled(fixedRate = 5000)
public void runEvery5Seconds() {
log.info(">>> running every 5 seconds");
}
}

And the main class:

RunningPeriodicTaskApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableScheduling
@SpringBootApplication
public class RunningPeriodicTaskApplication {
public static void main(String[] args) {
SpringApplication.run(RunningPeriodicTaskApplication.class, args);
}
}

You can use @Controller annotation for traditional Spring controllers.

Spring version 4.0 introduced the @RestController annotation in order to simplify the creation of RESTful web services. It combines @Controller and @ResponseBody annotations, which eliminates the need to annotate every request handling method of the controller class with the @ResponseBody annotation.

Letโ€™s see the same code example first with @Controller annotation and then with @RestController.

UserController.java
@Controller
@RequestMapping("users")
public class UserController {
@GetMapping("/{id}", produces = "application/json")
public @ResponseBody User getUser(@PathVariable int id) {
return findUserById(id);
}
private Book findUserById(int id) {
// perform DB search or whatever is needed
}
}

The same example becomes:

UserController.java
@RestController
@RequestMapping("rest-users")
public class UserController {
@GetMapping("/{id}", produces = "application/json")
public User getUser(@PathVariable int id) {
return findUserById(id);
}
private Book findUserById(int id) {
// perform DB search or whatever is needed
}
}

Within a traditional Spring controller itโ€™s very easy to serve a static HTML page. Given the following project structure:

  • Directorysrc.main
    • Directoryjava.com.pietropouzzi.test
      • Directorycontrollers
        • TestController.java
      • TestApplication.java
    • Directoryresources
      • Directorytemplates
        • hello.html
    • application.properties
  • pom.xml

Here are the files:

TestController.java
package com.pietropoluzzi.user_crud_server.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/test")
public class TestController {
@GetMapping("/hello")
public String helloPage() {
return "hello";
}
}

@RestController is usually used for returning raw data (e.g., JSON or strings). You can allow a @RestController to return an HTML view.

Given the following project structure:

  • Directorysrc.main
    • Directoryjava.com.pietropouzzi.test
      • Directorycontrollers
        • TestRestController.java
      • TestApplication.java
    • Directoryresources
      • Directorystatic
        • hello.html
    • application.properties
  • pom.xml

Here are the files:

TestRestController.java
package com.pietropoluzzi.test.controllers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@RestController
public class TestRestController {
@Autowired
private ResourceLoader resourceLoader;
@GetMapping("/rest-test/hello")
public ResponseEntity<String> getHtmlPage() throws IOException {
Resource resource = resourceLoader.getResource("classpath:static/hello.html");
String html = new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
return ResponseEntity.ok()
.contentType(MediaType.TEXT_HTML)
.body(html);
}
}

From JDK 6 to JDK 8, the @PostConstruct and @PreDestroy annotations were part of the standard Java libraries under the javax.annotation package. With JDK 9, the entire javax.annotation package was removed from the core Java modules and fully eliminated in JDK 11. In Jakarta EE 9, this package has been relocated to jakarta.annotation.


Java EE, known as Jakarta EE, is a platform for developing and deploying enterprise-level Java applications. It provides a set of specifications and APIs that extend Java SE (Standard Edition) to handle the complexities of large-scale, distributed, and networked applications.

@Slf4j allows you to easily use a static Logger instance called log within the annotated class.

Add the following directory within pom.xml file:

pom.xml
<project>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

Then you can annotate the class like so:

MainClass.java
@Slf4j
public class MainClass {
public void someMethod() {
log.info("someMethod() invocation");
}
}

@Slf4j creates under the hood the following log implementation:

private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MainClass.class);

Use Spring ResponseEntity to manipulate HTTP response.

ResponseEntity represents the whole HTTP response: status code, headers, and body. You can use it to fully configure the HTTP response.

It provides two nested builder interfaces: HeadersBuilder and its sub-interface BodyBuilder. You can access their capabilities through the static methods of ResponseEntity.

The simplest case is a response with a body and HTTP 200 response code:

Controller.java
@PostMapping("/ok")
public ResponseEntity<String> ok() {
return ResponseEntity.ok("Hello, World!");
}

You can access BodyBuilder capabilities through the static methods of ResponseEntity:

Controller.java
@PostMapping("/body-builder-ok")
public ResponseEntity<String> bodyBuilderOk() {
return ResponseEntity.ok().body("OK request body");
}
@PostMapping("/body-builder-bad-request")
public ResponseEntity<String> bodyBuilderBadRequest() {
return ResponseEntity.badRequest().body("Bad Request body");
}
@PostMapping("/body-builder-accepted")
public ResponseEntity<String> bodyBuilderSuccess() {
return ResponseEntity.status(HttpStatus.ACCEPTED).body("Accepted request body");
}

You can also create and return a new ResponseEntity instance by passing a type <T> body and an HttpStatusCode implementation:

Controller.java
@PostMapping("/ok-v2")
public ResponseEntity<String> ok_v2() {
return new ResponseEntity<>("Hello, World!", HttpStatus.OK);
}

Set custom headers to the response in two different ways:

Controller.java
@PostMapping("/body-builder-custom-headers")
public ResponseEntity<String> bodyBuilderCustomHeaders() {
return ResponseEntity.ok().header("Custom-Header", "foo").body("ok request body - check the headers");
}
@PostMapping("/custom-header")
public ResponseEntity<?> customHeader() {
HttpHeaders headers = new HttpHeaders();
headers.add("Custom-Header", "foo");
return new ResponseEntity<>("Hello, World!", headers, HttpStatus.OK);
}

Letโ€™s take a more advanced example. You want to perform a POST that adds a new entity (e.g., a user) to a database. Entity properties must meet certain constraints, e.g., age must be an integer greater than zero or name cannot start with a digit.

The project structure looks like:

  • Directorycom.pietropoluzzi.http_response
    • Directorycontrollers
      • UserController.java
    • Directorymodels
      • UserModel.java
      • UserCreationModel.java
    • Directoryservices
      • UserService.java
UserModel.java
package com.pietropoluzzi.http_response.models;
record UserModel(Long id, String name, Integer age) {}

You can now perform the POST in the following ways to test the different scenarios:

  • Get an HTTP status 200:

    Windows PowerShell
    curl -Method POST http://localhost:8080/users `
    -Headers @{ "Content-Type" = "application/json" } `
    -Body '{ "name": "Pit", "age": 24 }'
  • Get an HTTP status 400 because of negative age:

    Windows PowerShell
    curl -Method POST http://localhost:8080/users `
    -Headers @{ "Content-Type" = "application/json" } `
    -Body '{ "name": "Pit", "age": -10 }'
  • Get an HTTP status 400 because of name stars with a digit:

    Windows PowerShell
    curl -Method POST http://localhost:8080/users `
    -Headers @{ "Content-Type" = "application/json" } `
    -Body '{ "name": "123Pit", "age": 24 }'

Letโ€™s take the example of a large project. To simplify developing and debugging, the project can be subdivided into many modules. Modules can interact with each other. They can be full Spring Boot applications or just plain Java code.

You must write a โ€œparentโ€ pom.xml and many โ€œchildโ€ pom.xml files (one for each module). Modules skeleton can be created using Spring initializr web tool.

In the example Iโ€™m going to show you, the โ€œparentโ€ is called com.pietropoluzzi.spring_notes, and the modules are core and rest_server.

  • Directorycore
    • Directorysrc/main
      • Directoryjava/com/pietropoluzzi/spring_notes/core/ here is the code
        • โ€ฆ
      • Directoryresources
        • application.properties
    • pom.xml core pom
  • Directoryrest_server
    • Directorysrc/main/
      • java/com/pietropoluzzi/spring_notes/rest_server here is the code
      • Directoryresources/
        • โ€ฆ
    • pom.xml REST server pom
  • pom.xml parent pom

Here are the files:

pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.pietropoluzzi.spring_notes</groupId>
<artifactId>spring_notes</artifactId>
<version>0.0.1</version>
<name>spring_notes</name>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
<packaging>pom</packaging>
<description>Spring Notes parent project</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
<spring.boot.version>3.5.3</spring.boot.version>
</properties>
<modules>
<module>core</module>
<module>rest_server</module>
</modules>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

Docs: