Spring Boot assembles a Spring application from your code, selected dependencies, configuration, and auto-configuration. This lesson targets Spring Boot 4.1, which requires Java 17 or later. You will generate a small project, understand what the application class activates, run one HTTP endpoint, and verify both the success and failure paths.
The goal is not to memorize generated folders. It is to understand the startup chain: the JVM runs main(), SpringApplication creates the application context, component scanning discovers your classes, auto-configuration contributes infrastructure based on the classpath, and the embedded server begins accepting requests.
Spring Initializr creates a build that matches the Spring Boot version and dependencies you select. Choose Java, a supported build tool, Java 17 or newer, and only the starters required for the first capability. For an MVC JSON endpoint, Spring Web is enough; add validation, persistence, security, or observability when the application actually needs them.
A starter is a curated dependency entry point, not a framework feature by itself. The build tool resolves its transitive libraries. Keep the generated wrapper files so every developer and CI job can use the project-approved build-tool version without relying on a machine-wide installation.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<!-- Spring Boot Parent: manages all dependency versions -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>my-spring-app</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Web MVC + embedded Tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JPA + Hibernate -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Thymeleaf template engine -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Actuator: health, metrics, info endpoints -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties or application.yaml supplies defaults, while environment variables and command-line arguments can override deployment-specific values. Keep secrets outside version-controlled files. Group related custom values with typed configuration properties instead of scattering @Value expressions across the codebase.
Profiles can select environment-specific beans or configuration, but they should not turn one application into several unrelated products. A production artifact should be built once and configured at runtime. Log the active profiles and sanitized configuration decisions so deployment mistakes are diagnosable without exposing credentials.
# Server configuration
server.port=8080
server.servlet.context-path=/api
# Database configuration
spring.datasource.url=jdbc:mysql://localhost:3306/mydb?useSSL=false
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA/Hibernate
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
# Logging
logging.level.org.springframework=INFO
logging.level.com.example=DEBUG
logging.file.name=logs/app.log
# Actuator
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
# Custom properties
app.name=My Spring App
app.version=1.0.0
app.jwt.secret=mySecretKey123
# application.yml (YAML format - same config, different syntax)
server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb?useSSL=false
username: root
password: password
jpa:
hibernate:
ddl-auto: update
show-sql: true
logging:
level:
org.springframework: INFO
com.example: DEBUG
app:
name: My Spring App
version: 1.0.0
@SpringBootApplication combines configuration, auto-configuration, and component scanning. SpringApplication.run() creates and refreshes the context, then returns it. Place this class high enough in the package hierarchy for scanning to find your components; otherwise a controller can compile correctly yet never become a bean.
Auto-configuration is conditional. It reacts to classes, beans, and properties that are present, and backs away when you supply your own bean. When startup fails, read the first meaningful cause and the condition evaluation information instead of adding annotations at random.
package com.example.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class HelloController {
@Value("${app.name}")
private String appName;
@GetMapping("/hello")
public String hello() {
return "Hello from " + appName + "!";
}
@GetMapping("/hello/{name}")
public String helloName(@PathVariable String name) {
return "Hello, " + name + "!";
}
@GetMapping("/greet")
public String greet(@RequestParam(defaultValue = "World") String name) {
return "Greetings, " + name + "!";
}
}
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
// App starts at http://localhost:8080
// Test: curl http://localhost:8080/api/hello
// Test: curl http://localhost:8080/api/hello/Alice
// Test: curl http://localhost:8080/api/greet?name=Bob
}
}
A small endpoint proves that scanning, MVC configuration, JSON conversion, and the embedded server all work. Keep the controller thin: bind and validate transport input, call a service, and convert the result into the intended HTTP response. Business rules should remain testable without starting a server.
package com.example.demo.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
class StatusController {
record Status(String state, String message) {}
@GetMapping("/api/status")
Status status() {
return new Status("ready", "Application context started");
}
}
{"state":"ready","message":"Application context started"}
Spring MVC maps GET /api/status to status(). The returned record is written as JSON by an HTTP message converter available through the web starter.
Run through the project wrapper, then call the endpoint from a browser or HTTP client. A clean startup log should show the application name, active profiles, server port, and completion time. Stop the process cleanly so lifecycle callbacks and resource pools can close.
If the port is occupied, choose another server.port rather than terminating an unknown process. If the endpoint returns 404, confirm the request path and package scanning. If startup reports an unsatisfied dependency, identify the missing bean and constructor path. If the JVM version is unsupported, fix the toolchain before changing application code.
Try this next
0 of 3 completed
No. The official system requirements specify Java 17 as the minimum, although newer supported JDKs can be used.
No, but it provides a reliable project baseline with compatible plugin and dependency management.
A common cause is that the controller is outside component scanning or the requested mapping differs from the declared path.
Explore 500+ free tutorials across 20+ languages and frameworks.