Java · lesson 49 of 66
Beginner
17- 1 Introduction to Java
- 2 Setting Up Java
- 3 Data Types in Java
- 4 Operators in Java
- 5 Variables and Operators in Java
- 6 Control Flow in Java
- 7 Arrays in Java
- 8 Functions (Methods) in Java
- 9 Strings in Java
- 10 Methods in Java
- 11 Java OOP — Complete Guide
- 12 Introduction to Object-Oriented Programming in Java
- 13 Encapsulation in Java
- 14 Inheritance in Java
- 15 Polymorphism in Java
- 17 Abstraction in Java
- 18 Error Types in Java
Intermediate
38- 16 Regular Expressions in Java
- 19 Interfaces in Java — A Deep Dive
- 20 Abstract Classes in Java
- 21 Method Overloading in Java
- 22 Composition vs Inheritance in Java
- 23 Generics in Java
- 27 Exception Handling in Java
- 28 Java Streams API
- 29 Collections Framework in Java
- 30 Optional in Java
- 31 File Handling in Java
- 32 Records in Java
- 33 Java 8 Features
- 34 Sealed Classes and Interfaces in Java
- 35 Multithreading in Java
- 37 JDBC — Database Access in Java
- 39 Maven — Java Build and Dependency Management
- 42 Gradle — Modern Java Build Tool
- 44 Unit Testing with JUnit 5 and Mockito
- 45 Logging in Java with SLF4J and Logback
- 49 Spring Boot — Introduction
- 50 Dependency Injection in Spring Boot
- 51 Java Project Ideas
- 52 Building REST APIs with Spring Boot
- 53 Validation in Spring Boot
- 54 Spring Data JPA — Database Access
- 55 Exception Handling in Spring Boot
- 56 Spring Security — Authentication and Authorization
- 57 JWT Authentication in Spring Boot
- 58 Dockerizing Spring Boot Applications
- 59 Java Enums: Beyond Simple Constants
- 60 Inner and Anonymous Classes in Java
- 61 Java Generics: Bounded Types, Wildcards, and Generic Methods
- 62 Java Date/Time API: LocalDate, LocalDateTime, and Duration
- 63 Java NIO.2: Paths, Files, and WatchService
- 64 Java Annotations: Custom Types and Meta-Information
- 65 Java Reflection: Dynamic Type Inspection and Method Invocation
- 66 Java Localization: ResourceBundle and Locale Support
Advanced
11Spring Boot — Introduction
Build and run a Spring Boot 3 app: the entry point, a REST controller, application.properties, command-line overrides, profiles and Actuator health.
By SkillByExample · Updated
Spring Boot is the standard way to build Java backend services. It sits on top of Spring Framework
and makes the configuration decisions for you based on what is on the classpath: add the web starter
and you get an embedded Tomcat, JSON serialisation and an MVC dispatcher without writing any setup.
Every example below is one Maven project built with Spring Boot 3.5 on JDK 21, packaged with
mvn package and started with java -jar; the curl calls ran against the live app.
The project
start.spring.io generates this skeleton; the only parts that matter are the parent POM, which pins compatible versions of every Spring library, and the starters you depend on.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.16</version>
</parent>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency> <!-- Spring MVC + embedded Tomcat + Jackson -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency> <!-- /actuator/health and friends -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<!-- build/plugins: spring-boot-maven-plugin repackages the JAR as executable -->
src/main/java/com/example/demo/DemoApplication.java entry point
src/main/java/com/example/demo/HelloController.java REST endpoints
src/main/resources/application.properties configuration
pom.xml
A starter is a curated bundle: spring-boot-starter-web alone brings in Spring MVC, Tomcat and
Jackson at versions the parent POM has already tested together, so no <version> tags appear on
them.
The entry point and a first endpoint
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
package com.example.demo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class HelloController {
@Value("${app.greeting:Hello}") // property, with "Hello" as the fallback
private String greeting;
@GetMapping("/hello")
public String hello() {
return greeting + ", Spring Boot!";
}
@GetMapping("/hello/{name}")
public String helloName(@PathVariable String name) {
return greeting + ", " + name + "!";
}
record Greeting(String message, int length) {}
@GetMapping("/greeting") // a record is serialised to JSON by Jackson
public Greeting greeting(@RequestParam(defaultValue = "World") String name) {
return new Greeting(greeting + ", " + name, name.length());
}
}
$ mvn -q -B package -DskipTests
$ java -jar target/demo-0.0.1-SNAPSHOT.jar
...
:: Spring Boot :: (v3.5.16)
2026-10-07T21:56:12.814Z INFO 1942 --- [demo] [ main] com.example.demo.DemoApplication : Starting DemoApplication v0.0.1-SNAPSHOT using Java 21.0.12.1 with PID 1942 (/tmp/sb/intro/target/demo-0.0.1-SNAPSHOT.jar started by root in /tmp/sb/intro)
2026-10-07T21:56:12.816Z INFO 1942 --- [demo] [ main] com.example.demo.DemoApplication : No active profile set, falling back to 1 default profile: "default"
2026-10-07T21:56:13.708Z INFO 1942 --- [demo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
...
2026-10-07T21:56:14.234Z INFO 1942 --- [demo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2026-10-07T21:56:14.245Z INFO 1942 --- [demo] [ main] com.example.demo.DemoApplication : Started DemoApplication in 1.796 seconds (process running for 2.152)
$ curl -s -w '\n' localhost:8080/api/hello
Hello, Spring Boot!
$ curl -s -w '\n' localhost:8080/api/hello/Alice
Hello, Alice!
$ curl -s -w '\n' 'localhost:8080/api/greeting?name=Ada'
{"message":"Hello, Ada","length":3}
$ curl -s -w '\n' localhost:8080/api/greeting
{"message":"Hello, World","length":5}
$ curl -s -o /dev/null -w '%{http_code}\n' localhost:8080/api/nope
404
Nothing configured Tomcat, the port or JSON — @SpringBootApplication turned on auto-configuration,
which saw spring-boot-starter-web on the classpath and set all three up. Component scanning found
HelloController because it lives in the same package as DemoApplication; a controller in a
sibling package such as com.example.other would silently never be registered. A String return
value is written as plain text, while the record becomes JSON with its components as keys, in
declaration order.
application.properties and overriding it
Configuration lives in src/main/resources/application.properties, which is packaged inside the JAR.
spring.application.name=demo
app.greeting=Hello
management.endpoints.web.exposure.include=health,info
management.info.env.enabled=true
info.app.name=demo
info.app.version=0.0.1
$ java -jar target/demo-0.0.1-SNAPSHOT.jar --server.port=9090 --app.greeting=Howdy
...
2026-10-07T21:54:06.071Z INFO 1118 --- [demo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 9090 (http) with context path '/'
2026-10-07T21:54:06.086Z INFO 1118 --- [demo] [ main] com.example.demo.DemoApplication : Started DemoApplication in 1.925 seconds (process running for 2.252)
$ curl -s -w '\n' localhost:9090/api/hello/Bob
Howdy, Bob!
$ APP_GREETING=Bonjour java -jar target/demo-0.0.1-SNAPSHOT.jar
...
2026-10-07T21:54:11.813Z INFO 1175 --- [demo] [ main] com.example.demo.DemoApplication : Started DemoApplication in 1.987 seconds (process running for 2.338)
$ curl -s -w '\n' localhost:8080/api/hello/Bob
Bonjour, Bob!
The same JAR answered with three different greetings. Spring Boot layers property sources:
command-line --key=value arguments beat environment variables, which beat the packaged
application.properties. The environment variable name is the property upper-cased with dots turned
into underscores — APP_GREETING for app.greeting, SERVER_PORT for server.port — which is how
containers and CI inject configuration without rebuilding.
If a property has no value anywhere and no :default in the placeholder, the app refuses to start.
With ${app.greeting} (no fallback) and the key removed from the file:
$ java -jar target/demo-0.0.1-SNAPSHOT.jar 2>&1 | grep -m1 'Could not resolve'
Caused by: org.springframework.util.PlaceholderResolutionException: Could not resolve placeholder 'app.greeting' in value "${app.greeting}"
Failing at startup is deliberate: a missing database password should stop the deploy, not surface as
a null on the first request.
Profiles
A profile file holds only the keys that differ for one environment.
# src/main/resources/application-dev.properties
server.port=8081
app.greeting=Hi from dev
$ java -jar target/demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
2026-10-07T21:54:15.894Z INFO 1233 --- [demo] [ main] com.example.demo.DemoApplication : The following 1 profile is active: "dev"
...
2026-10-07T21:54:17.531Z INFO 1233 --- [demo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8081 (http) with context path '/'
2026-10-07T21:54:17.543Z INFO 1233 --- [demo] [ main] com.example.demo.DemoApplication : Started DemoApplication in 2.052 seconds (process running for 2.421)
$ curl -s -w '\n' localhost:8081/api/hello
Hi from dev, Spring Boot!
The startup line now names the active profile instead of falling back to 1 default profile.
application.properties still loaded — spring.application.name=demo shows up as [demo] in the
log — and application-dev.properties was applied on top, so its two keys won. Set
SPRING_PROFILES_ACTIVE=dev to do the same from the environment.
Actuator
spring-boot-starter-actuator adds operational endpoints. Only health is exposed over HTTP by
default; the management.endpoints.web.exposure.include=health,info line above added info, and the
info.* keys supply its content.
$ curl -s -w '\n' localhost:8080/actuator/health
{"status":"UP"}
$ curl -s -w '\n' localhost:8080/actuator/info
{"app":{"name":"demo","version":"0.0.1"}}
Load balancers and Kubernetes probes poll /actuator/health to decide whether an instance should
receive traffic. When a database is configured, its connection check is folded into that UP
automatically. Keep the exposure list short — endpoints such as env and heapdump leak secrets.
Practice
- Add a
GET /api/add?a=2&b=40endpoint that returns{"a":2,"b":40,"sum":42}. What status does it return whenbis missing?
Solution
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MathController {
record Sum(int a, int b, int sum) {}
@GetMapping("/api/add")
public Sum add(@RequestParam int a, @RequestParam int b) {
return new Sum(a, b, a + b);
}
}
$ curl -s -w '\n' 'localhost:8080/api/add?a=2&b=40'
{"a":2,"b":40,"sum":42}
$ curl -s -o /dev/null -w '%{http_code}\n' 'localhost:8080/api/add?a=2'
400
$ curl -s -o /dev/null -w '%{http_code}\n' 'localhost:8080/api/add?a=2&b=x'
400
@RequestParam is required by default, and x cannot be converted to int, so both bad requests
are rejected with 400 before the method runs.
- Add a
prodprofile that serves on port 9000 and greets withWelcome.
Solution
# src/main/resources/application-prod.properties
server.port=9000
app.greeting=Welcome
$ java -jar target/demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod
2026-10-07T21:54:43.245Z INFO 1427 --- [demo] [ main] com.example.demo.DemoApplication : The following 1 profile is active: "prod"
2026-10-07T21:54:44.722Z INFO 1427 --- [demo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 9000 (http) with context path '/'
$ curl -s -w '\n' localhost:9000/api/hello/Ada
Welcome, Ada!
- Delete the
app.greetingline fromapplication.properties. Predict what/api/helloreturns with@Value("${app.greeting:Hello}"), and what happens with@Value("${app.greeting}").
Solution
$ curl -s -w '\n' localhost:8080/api/hello
Hello, Spring Boot!
With the :Hello fallback the app starts and uses the default. Without it, startup fails with the
Could not resolve placeholder 'app.greeting' error shown in the properties section.