Spring Boot 4 Cheat Sheet

Commands

./mvnw spring-boot:run          ./mvnw test          ./mvnw package
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev
./mvnw spring-boot:build-image  ./mvnw dependency:tree
java -jar target/app.jar --server.port=9000 --spring.profiles.active=prod

Application class

@SpringBootApplication
@ImportHttpServices(group = "books", basePackages = "com.example.clients")
public class DemoApplication {
    public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }
}

Stereotypes and wiring

@Component  @Service  @Repository  @RestController  @Configuration  @Bean
@Primary    @Qualifier("name")     @Scope("prototype")
@PostConstruct  @PreDestroy        @Profile("!prod")
@ConditionalOnProperty  @ConditionalOnMissingBean
@Service
class S { private final Repo repo; S(Repo repo) { this.repo = repo; } }   // constructor injection

Web

@RestController @RequestMapping("/api/books")
@GetMapping @PostMapping @PutMapping @PatchMapping @DeleteMapping
@PathVariable  @RequestParam  @RequestBody  @RequestHeader  @CookieValue
@ResponseStatus(HttpStatus.CREATED)   @CrossOrigin   @GetMapping(version = "1.2+")
ResponseEntity.ok(b)  .created(uri).body(b)  .noContent().build()  .notFound().build()
StatusWhen
200 / 201 / 204ok / created / no content
400 / 401 / 403 / 404 / 409invalid / unauthenticated / forbidden / missing / conflict
500unhandled

Validation & errors

@Valid @RequestBody Req r
@NotNull @NotBlank @Size(max=100) @Min @Max @Email @Pattern @Past @Future
@RestControllerAdvice  @ExceptionHandler(X.class)
ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, "…")

Configuration

server.port=8080
spring.application.name=demo
spring.profiles.active=dev
logging.level.com.example=DEBUG
logging.console.enabled=true
spring.threads.virtual.enabled=true
spring.jpa.hibernate.ddl-auto=validate
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.probes.enabled=true
server.shutdown=graceful
@Value("${app.key:default}") String key;

@ConfigurationProperties("app.mail")
record MailProperties(String from, String host, int port) {}

Precedence: command line → environment → application-{profile}application → code.

Data

@Entity @Id @GeneratedValue @Column(nullable = false) @ManyToOne @OneToMany
interface BookRepo extends JpaRepository<Book, Long> {
    List<Book> findByAuthor(String a);
    Optional<Book> findByTitleIgnoreCase(String t);
    @Query("select b from Book b where b.year > :y") List<Book> after(int y);
}
repo.save(b)  repo.findById(id)  repo.findAll(PageRequest.of(0, 20, Sort.by("title")))
@Transactional  @Transactional(readOnly = true)

HTTP clients

client.get().uri("/books/{id}", id).retrieve().body(Book.class);
client.post().uri("/books").body(req).retrieve().body(Book.class);
@HttpExchange("/books")
interface BookApi {
    @GetExchange("/{id}") Book byId(@PathVariable long id);
    @PostExchange Book create(@RequestBody BookRequest r);
}
spring.http.serviceclient.books.base-url=https://api.example.com
spring.http.clients.read-timeout=2s

Resilience

@EnableResilientMethods
@Retryable(includes = X.class, maxRetries = 4, delay = 100, multiplier = 2, maxDelay = 1000)
@ConcurrencyLimit(10)

Async, scheduling, events

@EnableAsync @Async CompletableFuture<T> 
@EnableScheduling
@Scheduled(fixedRate = 60_000) @Scheduled(fixedDelay = 5_000) @Scheduled(cron = "0 0 3 * * *")
events.publishEvent(new BookCreated(id));  @EventListener  @TransactionalEventListener

Caching

@EnableCaching  @Cacheable("books")  @CachePut(value="books", key="#b.id")  @CacheEvict("books")

Security

@EnableWebSecurity  @EnableMethodSecurity  @PreAuthorize("hasRole('ADMIN')")
http.authorizeHttpRequests(a -> a.requestMatchers("/public/**").permitAll()
                                 .anyRequest().authenticated());

Null safety

@NullMarked          // package-info.java
@Nullable Book find(String title);
List<@Nullable String>   vs   @Nullable List<String>

Testing

@SpringBootTest(webEnvironment = RANDOM_PORT)   @WebMvcTest(C.class)   @DataJpaTest
@RestClientTest   @JsonTest   @ActiveProfiles("test")
@MockitoBean  @MockitoSpyBean
client.get().uri("/api/books/1").exchange().expectStatus().isOk()
      .expectBody().jsonPath("$.title").isEqualTo("Dune");

Actuator

/actuator/health   /health/liveness   /health/readiness
/actuator/info     /actuator/metrics  /actuator/loggers   /actuator/prometheus

Spring Boot 3 → Spring Boot 4

OldNew
@MockBean@MockitoBean
RestTemplateRestClient
spring.dao.exceptiontranslation.enabledspring.persistence.exceptiontranslation.enabled
management.tracing.enabledmanagement.tracing.export.enabled
hand-written client classes@HttpExchange + @ImportHttpServices
/v1/ controller copies@GetMapping(version = "…")