Spring Boot in Action Skill
Apply the practices from Craig Walls' "Spring Boot in Action" to review existing code and write new Spring Boot applications. This skill operates in two modes: Review Mode (analyze code for violations of Spring Boot idioms) and Write Mode (produce clean, idiomatic Spring Boot from scratch).
The core philosophy: Spring Boot removes boilerplate through auto-configuration, starter dependencies, and sensible defaults. Fight the framework only when necessary — and when you do, prefer application.properties over code.
Reference Files
practices-catalog.md — Before/after examples for auto-configuration, starters, properties, profiles, security, testing, Actuator, and deployment
How to Use This Skill
Before responding, read practices-catalog.md for the topic at hand. For configuration issues read the properties/profiles section. For test code read the testing section. For a full review, read all sections.
Mode 1: Code Review
When the user asks you to review Spring Boot code, follow this process:
Step 1: Identify the Layer
Determine whether the code is a controller, service, repository, configuration class, or test. Review focus shifts by layer.
Step 2: Analyze the Code
Check these areas in order of severity:
Auto-Configuration (Ch 2, 3): Is auto-configuration being fought manually? Look for @Bean definitions that replicate what Spring Boot already provides (DataSource, Jackson, Security, etc.). Remove manual config where auto-config suffices.
Starter Dependencies (Ch 2): Are dependencies declared individually instead of using starters? spring-boot-starter-web, spring-boot-starter-data-jpa, spring-boot-starter-security etc. bundle correct transitive dependencies and version-manage them.
Externalized Configuration (Ch 3): Are values hardcoded that belong in application.properties? Ports, URLs, credentials, timeouts should all be externalized. Use @ConfigurationProperties for type-safe config objects; use @Value only for single values.
Profiles (Ch 3): Is environment-specific config (dev DB vs prod DB) handled with if statements or system properties? Use @Profile and application-{profile}.properties instead.
Security (Ch 3): Is WebSecurityConfigurerAdapter extended when simple property-based config would suffice? Is HTTP Basic enabled in production? Are actuator endpoints exposed without auth?
Testing (Ch 4):
- Use
@SpringBootTest for full integration tests, not raw new MyService()
- Use
@WebMvcTest for controller-only tests (no full context)
- Use
@DataJpaTest for repository tests (in-memory DB, no web layer)
- Use
MockMvc for controller assertions without starting a server
- Use
@MockBean to replace real beans with mocks in slice tests
- Avoid
@SpringBootTest(webEnvironment = RANDOM_PORT) unless testing the full HTTP stack
- Flag missing negative test cases: if there is no test for "not found" (expecting 404), or no test verifying a POST returns 201 with a Location header, call these out explicitly as missing coverage
Actuator (Ch 7): Is the application missing health/metrics endpoints? Is /actuator fully exposed without security? Are custom health indicators implemented for critical dependencies?
Deployment (Ch 8): Is spring.profiles.active set for production? Is database migration (Flyway/Liquibase) configured? Is the app packaged as a self-contained JAR (preferred) or WAR?
General Idioms:
- Constructor injection over field injection (
@Autowired on fields)
@RestController = @Controller + @ResponseBody — use it for REST APIs
- Return
ResponseEntity<T> from controllers when status codes matter
Optional<T> from repository methods, never null
Step 3: Calibrate Before Reporting
Do NOT manufacture issues. If the code already follows idiomatic Spring Boot practices, say so explicitly and acknowledge what is correct. Do not invent problems to fill a review.
Rule: Only flag what is actually wrong in the code shown. Do not raise issues about code or configuration files that are NOT present in the review.
- Missing test files are not an issue in a review of production code unless the prompt asks about test coverage.
- Missing Flyway/Liquibase is not an issue unless
spring.jpa.hibernate.ddl-auto=create is used in a production context.
spring.jpa.hibernate.ddl-auto=validate combined with H2 or any datasource is NOT a conflict — validate is a safe, correct choice.
- Missing
spring.application.name is NOT an issue unless service discovery is involved.
These patterns are correct and must NOT be flagged as issues:
@SpringBootApplication on the main class — correct (Ch 1)
- Constructor injection without
@Autowired — correct; Spring auto-wires single-constructor beans (Ch 2)
ResponseEntity.created(URI.create("/api/resource/" + id)).body(saved) — correct; relative URIs are standard practice here (Ch 2)
repo.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)) — correct pattern (Ch 2); at most suggest adding a descriptive message string, do NOT recommend replacing it with a domain exception + @RestControllerAdvice
SLF4J logger via LoggerFactory.getLogger(...) — correct (Ch 3)
${ENV_VAR:default} syntax in application.properties — correct externalization (Ch 3)
management.endpoints.web.exposure.include=health,info — correct Actuator lockdown (Ch 7)
When the code is idiomatic, the only acceptable suggestions are:
- Add a descriptive message to
ResponseStatusException if one is missing (minor suggestion only)
- Add
spring-boot-starter-actuator dependency if the management.* properties are present but the starter might not be (minor suggestion only)
Do not suggest Flyway, @Valid, test classes, or @RestControllerAdvice unless the code has a concrete problem that requires them.
Step 4: Report Findings
For each genuine issue, report:
- Chapter reference (e.g., "Ch 3: Externalized Configuration")
- Location in the code
- What's wrong (the anti-pattern)
- How to fix it (the Spring Boot idiomatic way)
- Priority: Critical (security/bugs), Important (maintainability), Suggestion (polish)
Step 5: Provide Fixed Code
Offer a corrected version with comments explaining each change.
Mode 2: Writing New Code
When the user asks you to write new Spring Boot code, apply these core principles:
Project Bootstrap (Ch 1, 2)
Start with Spring Initializr (Ch 1). Use start.spring.io or spring init CLI. Select starters upfront — don't add raw dependencies manually.
Use starters, not individual dependencies (Ch 2). spring-boot-starter-web includes Tomcat, Spring MVC, Jackson, and logging at compatible versions. Never declare spring-webmvc + jackson-databind + tomcat-embed-core separately.
The main class is the only required boilerplate (Ch 2):
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
@SpringBootApplication = @Configuration + @EnableAutoConfiguration + @ComponentScan.
Configuration (Ch 3)
Externalize all environment-specific values (Ch 3). Nothing deployment-specific belongs in code. Use application.properties / application.yml for defaults.
Use @ConfigurationProperties for grouped config (Ch 3). Bind a prefix to a POJO — type-safe, IDE-friendly, testable:
@ConfigurationProperties(prefix = "app.mail")
@Component
public class MailProperties {
private String host;
private int port = 25;
// getters + setters
}
Use profiles for environment differences (Ch 3). application-dev.properties overrides application.properties when spring.profiles.active=dev. Never use if (env.equals("production")) in code.
Override auto-configuration surgically (Ch 3). Use spring.* properties first. Only define a @Bean when properties are insufficient. Annotate with @ConditionalOnMissingBean if providing a fallback.
Customize error pages declaratively (Ch 3). Place error/404.html, error/500.html in src/main/resources/templates/error/. No custom ErrorController needed for basic cases.
Security (Ch 3)
Extend WebSecurityConfigurerAdapter only for custom rules (Ch 3). For simple HTTP Basic with custom users, spring.security.user.name / spring.security.user.password properties suffice.
Always secure Actuator endpoints in production (Ch 7). Expose only health and info publicly; require authentication for env, beans, mappings, shutdown.
REST Controllers (Ch 2)
Use @RestController for API endpoints (Ch 2). Eliminates @ResponseBody on every method.
Return ResponseEntity<T> when HTTP status matters (Ch 2). ResponseEntity.ok(body), ResponseEntity.notFound().build(), ResponseEntity.status(201).body(created).
Use constructor injection, not field injection (Ch 2). Constructor injection makes dependencies explicit and enables testing without Spring context:
// Prefer this:
@RestController
public class BookController {
private final BookRepository repo;
public BookController(BookRepository repo) { this.repo = repo; }
}
Use Optional from repository queries (Ch 2). repo.findById(id).orElseThrow(() -> new ResponseStatusException(NOT_FOUND)).
Testing (Ch 4)
Match test slice to the layer being tested (Ch 4):
- Web layer only →
@WebMvcTest(MyController.class) + MockMvc
- Repository only →
@DataJpaTest
- Full app →
@SpringBootTest
- External service →
@MockBean to replace
Use MockMvc for controller assertions without starting a server (Ch 4):
mockMvc.perform(get("/books/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.title").value("Spring Boot in Action"));
Use @MockBean to isolate the unit under test (Ch 4). Replaces the real bean in the Spring context with a Mockito mock — cleaner than manual wiring.
Test security explicitly (Ch 4). Use .with(user("admin").roles("ADMIN")) or @WithMockUser to assert secured endpoints reject unauthenticated requests.
Actuator (Ch 7)
Enable Actuator in every production app (Ch 7). Add spring-boot-starter-actuator. At minimum expose health and info.
Write custom HealthIndicator for critical dependencies (Ch 7):
@Component
public class DatabaseHealthIndicator implements HealthIndicator {
@Override
public Health health() {
return canConnect() ? Health.up().build()
: Health.down().withDetail("reason", "timeout").build();
}
}
Add custom metrics via MeterRegistry (Ch 7). Counter, gauge, timer — gives Prometheus/Grafana visibility into business events.
Restrict Actuator exposure in production (Ch 7):
management.endpoints.web.exposure.include=health,info
management.endpoint.health.show-details=when-authorized
Deployment (Ch 8)
Package as an executable JAR by default (Ch 8). mvn package produces a fat JAR with embedded Tomcat. Run with java -jar app.jar. No application server needed.
Create a production profile (Ch 8). application-production.properties sets spring.datasource.url, disables dev tools, sets log levels to WARN.
Use Flyway or Liquibase for database migrations (Ch 8). Add spring-boot-starter-flyway; place scripts in classpath:db/migration/V1__init.sql. Never use spring.jpa.hibernate.ddl-auto=create in production.
Starter Cheat Sheet (Ch 2, Appendix B)
| Need |
Starter |
| REST API |
spring-boot-starter-web |
| JPA / Hibernate |
spring-boot-starter-data-jpa |
| Security |
spring-boot-starter-security |
| Observability |
spring-boot-starter-actuator |
| Testing |
spring-boot-starter-test |
| Thymeleaf views |
spring-boot-starter-thymeleaf |
| Redis cache |
spring-boot-starter-data-redis |
| Messaging |
spring-boot-starter-amqp |
| DB migration |
flyway-core |
Code Structure Template
// Main class (Ch 2)
@SpringBootApplication
public class LibraryApp {
public static void main(String[] args) {
SpringApplication.run(LibraryApp.class, args);
}
}
// Entity (Ch 2)
@Entity
public class Book {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String isbn;
// constructors, getters, setters
}
// Repository (Ch 2)
public interface BookRepository extends JpaRepository<Book, Long> {
List<Book> findByTitleContainingIgnoreCase(String title);
}
// Service (Ch 2) — constructor injection
@Service
public class BookService {
private final BookRepository repo;
public BookService(BookRepository repo) { this.repo = repo; }
public Book findById(Long id) {
return repo.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
}
// Controller (Ch 2)
@RestController
@RequestMapping("/api/books")
public class BookController {
private final BookService service;
public BookController(BookService service) { this.service = service; }
@GetMapping("/{id}")
public ResponseEntity<Book> getBook(@PathVariable Long id) {
return ResponseEntity.ok(service.findById(id));
}
@PostMapping
public ResponseEntity<Book> createBook(@RequestBody Book book) {
Book saved = service.save(book);
URI location = URI.create("/api/books/" + saved.getId());
return ResponseEntity.created(location).body(saved);
}
}
// application.properties (Ch 3)
// spring.datasource.url=jdbc:postgresql://localhost/library
// spring.datasource.username=${DB_USER}
// spring.datasource.password=${DB_PASS}
// spring.jpa.hibernate.ddl-auto=validate
// management.endpoints.web.exposure.include=health,info
// application-dev.properties (Ch 3)
// spring.datasource.url=jdbc:h2:mem:library
// spring.jpa.hibernate.ddl-auto=create-drop
// logging.level.org.springframework=DEBUG
Priority of Practices by Impact
Critical (Security & Correctness)
- Ch 3: Never hardcode credentials — use
${ENV_VAR} in properties
- Ch 3: Secure Actuator endpoints —
env, beans, shutdown must require auth
- Ch 4: Test secured endpoints explicitly — assert 401/403 on unauthenticated requests
- Ch 8: Never use
ddl-auto=create in production — use Flyway/Liquibase
Important (Idiom & Maintainability)
- Ch 2: Constructor injection over
@Autowired field injection
- Ch 2:
@RestController over @Controller + @ResponseBody for APIs
- Ch 2:
Optional from repository, never null
- Ch 3:
@ConfigurationProperties over scattered @Value for grouped config
- Ch 3: Profiles for environment differences — not
if statements
- Ch 4:
@WebMvcTest for controller tests — not full @SpringBootTest
- Ch 7: Custom
HealthIndicator for each critical dependency
Suggestions (Polish)
- Ch 3: Custom error pages in
templates/error/ — no code needed
- Ch 7: Custom metrics via
MeterRegistry for business events
- Ch 8: Production profile disables dev tools, sets WARN log level
- Ch 2: Use
spring-boot-devtools in dev for live reload
1---2name: spring-boot-in-action3description: Write and review Spring Boot applications using practices from "Spring Boot in Action" by Craig Walls. Covers auto-configuration, starter dependencies, externalizing configuration with properties and profiles, Spring Security, testing with MockMvc and @SpringBootTest, Spring Actuator for production observability, and deployment strategies (JAR, WAR, Cloud Foundry). Use when building Spring Boot apps, configuring beans, writing integration tests, setting up health checks, or deploying to production. Trigger on: "Spring Boot", "Spring", "@SpringBootApplication", "auto-configuration", "application.properties", "application.yml", "@RestController", "@Service", "@Repository", "SpringBootTest", "Actuator", "starter", ".java files", "Maven", "Gradle".4---56# Spring Boot in Action Skill78Apply the practices from Craig Walls' "Spring Boot in Action" to review existing code and write new Spring Boot applications. This skill operates in two modes: **Review Mode** (analyze code for violations of Spring Boot idioms) and **Write Mode** (produce clean, idiomatic Spring Boot from scratch).910The core philosophy: Spring Boot removes boilerplate through **auto-configuration**, **starter dependencies**, and **sensible defaults**. Fight the framework only when necessary — and when you do, prefer `application.properties` over code.1112## Reference Files1314- `practices-catalog.md` — Before/after examples for auto-configuration, starters, properties, profiles, security, testing, Actuator, and deployment1516## How to Use This Skill1718**Before responding**, read `practices-catalog.md` for the topic at hand. For configuration issues read the properties/profiles section. For test code read the testing section. For a full review, read all sections.1920---2122## Mode 1: Code Review2324When the user asks you to **review** Spring Boot code, follow this process:2526### Step 1: Identify the Layer27Determine whether the code is a controller, service, repository, configuration class, or test. Review focus shifts by layer.2829### Step 2: Analyze the Code3031Check these areas in order of severity:32331. **Auto-Configuration** (Ch 2, 3): Is auto-configuration being fought manually? Look for `@Bean` definitions that replicate what Spring Boot already provides (DataSource, Jackson, Security, etc.). Remove manual config where auto-config suffices.34352. **Starter Dependencies** (Ch 2): Are dependencies declared individually instead of using starters? `spring-boot-starter-web`, `spring-boot-starter-data-jpa`, `spring-boot-starter-security` etc. bundle correct transitive dependencies and version-manage them.36373. **Externalized Configuration** (Ch 3): Are values hardcoded that belong in `application.properties`? Ports, URLs, credentials, timeouts should all be externalized. Use `@ConfigurationProperties` for type-safe config objects; use `@Value` only for single values.38394. **Profiles** (Ch 3): Is environment-specific config (dev DB vs prod DB) handled with `if` statements or system properties? Use `@Profile` and `application-{profile}.properties` instead.40415. **Security** (Ch 3): Is `WebSecurityConfigurerAdapter` extended when simple property-based config would suffice? Is HTTP Basic enabled in production? Are actuator endpoints exposed without auth?42436. **Testing** (Ch 4):44 - Use `@SpringBootTest` for full integration tests, not raw `new MyService()`45 - Use `@WebMvcTest` for controller-only tests (no full context)46 - Use `@DataJpaTest` for repository tests (in-memory DB, no web layer)47 - Use `MockMvc` for controller assertions without starting a server48 - Use `@MockBean` to replace real beans with mocks in slice tests49 - Avoid `@SpringBootTest(webEnvironment = RANDOM_PORT)` unless testing the full HTTP stack50 - Flag missing negative test cases: if there is no test for "not found" (expecting 404), or no test verifying a POST returns 201 with a Location header, call these out explicitly as missing coverage51527. **Actuator** (Ch 7): Is the application missing health/metrics endpoints? Is `/actuator` fully exposed without security? Are custom health indicators implemented for critical dependencies?53548. **Deployment** (Ch 8): Is `spring.profiles.active` set for production? Is database migration (Flyway/Liquibase) configured? Is the app packaged as a self-contained JAR (preferred) or WAR?55569. **General Idioms**:57 - Constructor injection over field injection (`@Autowired` on fields)58 - `@RestController` = `@Controller` + `@ResponseBody` — use it for REST APIs59 - Return `ResponseEntity<T>` from controllers when status codes matter60 - `Optional<T>` from repository methods, never `null`6162### Step 3: Calibrate Before Reporting6364**Do NOT manufacture issues.** If the code already follows idiomatic Spring Boot practices, say so explicitly and acknowledge what is correct. Do not invent problems to fill a review.6566**Rule: Only flag what is actually wrong in the code shown. Do not raise issues about code or configuration files that are NOT present in the review.**67- Missing test files are not an issue in a review of production code unless the prompt asks about test coverage.68- Missing Flyway/Liquibase is not an issue unless `spring.jpa.hibernate.ddl-auto=create` is used in a production context.69- `spring.jpa.hibernate.ddl-auto=validate` combined with H2 or any datasource is NOT a conflict — validate is a safe, correct choice.70- Missing `spring.application.name` is NOT an issue unless service discovery is involved.7172These patterns are **correct** and must NOT be flagged as issues:73- `@SpringBootApplication` on the main class — correct (Ch 1)74- Constructor injection without `@Autowired` — correct; Spring auto-wires single-constructor beans (Ch 2)75- `ResponseEntity.created(URI.create("/api/resource/" + id)).body(saved)` — correct; relative URIs are standard practice here (Ch 2)76- `repo.findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND))` — correct pattern (Ch 2); at most suggest adding a descriptive message string, do NOT recommend replacing it with a domain exception + `@RestControllerAdvice`77- `SLF4J` logger via `LoggerFactory.getLogger(...)` — correct (Ch 3)78- `${ENV_VAR:default}` syntax in `application.properties` — correct externalization (Ch 3)79- `management.endpoints.web.exposure.include=health,info` — correct Actuator lockdown (Ch 7)8081**When the code is idiomatic, the only acceptable suggestions are:**821. Add a descriptive message to `ResponseStatusException` if one is missing (minor suggestion only)832. Add `spring-boot-starter-actuator` dependency if the `management.*` properties are present but the starter might not be (minor suggestion only)84Do not suggest Flyway, `@Valid`, test classes, or `@RestControllerAdvice` unless the code has a concrete problem that requires them.8586### Step 4: Report Findings87For each genuine issue, report:88- **Chapter reference** (e.g., "Ch 3: Externalized Configuration")89- **Location** in the code90- **What's wrong** (the anti-pattern)91- **How to fix it** (the Spring Boot idiomatic way)92- **Priority**: Critical (security/bugs), Important (maintainability), Suggestion (polish)9394### Step 5: Provide Fixed Code95Offer a corrected version with comments explaining each change.9697---9899## Mode 2: Writing New Code100101When the user asks you to **write** new Spring Boot code, apply these core principles:102103### Project Bootstrap (Ch 1, 2)1041051. **Start with Spring Initializr** (Ch 1). Use `start.spring.io` or `spring init` CLI. Select starters upfront — don't add raw dependencies manually.1061072. **Use starters, not individual dependencies** (Ch 2). `spring-boot-starter-web` includes Tomcat, Spring MVC, Jackson, and logging at compatible versions. Never declare `spring-webmvc` + `jackson-databind` + `tomcat-embed-core` separately.1081093. **The main class is the only required boilerplate** (Ch 2):110 ```java111 @SpringBootApplication112 public class MyApp {113 public static void main(String[] args) {114 SpringApplication.run(MyApp.class, args);115 }116 }117 ```118 `@SpringBootApplication` = `@Configuration` + `@EnableAutoConfiguration` + `@ComponentScan`.119120### Configuration (Ch 3)1211224. **Externalize all environment-specific values** (Ch 3). Nothing deployment-specific belongs in code. Use `application.properties` / `application.yml` for defaults.1231245. **Use `@ConfigurationProperties` for grouped config** (Ch 3). Bind a prefix to a POJO — type-safe, IDE-friendly, testable:125 ```java126 @ConfigurationProperties(prefix = "app.mail")127 @Component128 public class MailProperties {129 private String host;130 private int port = 25;131 // getters + setters132 }133 ```1341356. **Use profiles for environment differences** (Ch 3). `application-dev.properties` overrides `application.properties` when `spring.profiles.active=dev`. Never use `if (env.equals("production"))` in code.1361377. **Override auto-configuration surgically** (Ch 3). Use `spring.*` properties first. Only define a `@Bean` when properties are insufficient. Annotate with `@ConditionalOnMissingBean` if providing a fallback.1381398. **Customize error pages declaratively** (Ch 3). Place `error/404.html`, `error/500.html` in `src/main/resources/templates/error/`. No custom `ErrorController` needed for basic cases.140141### Security (Ch 3)1421439. **Extend `WebSecurityConfigurerAdapter` only for custom rules** (Ch 3). For simple HTTP Basic with custom users, `spring.security.user.name` / `spring.security.user.password` properties suffice.14414510. **Always secure Actuator endpoints in production** (Ch 7). Expose only `health` and `info` publicly; require authentication for `env`, `beans`, `mappings`, `shutdown`.146147### REST Controllers (Ch 2)14814911. **Use `@RestController` for API endpoints** (Ch 2). Eliminates `@ResponseBody` on every method.15015112. **Return `ResponseEntity<T>` when HTTP status matters** (Ch 2). `ResponseEntity.ok(body)`, `ResponseEntity.notFound().build()`, `ResponseEntity.status(201).body(created)`.15215313. **Use constructor injection, not field injection** (Ch 2). Constructor injection makes dependencies explicit and enables testing without Spring context:154 ```java155 // Prefer this:156 @RestController157 public class BookController {158 private final BookRepository repo;159 public BookController(BookRepository repo) { this.repo = repo; }160 }161 ```16216314. **Use `Optional` from repository queries** (Ch 2). `repo.findById(id).orElseThrow(() -> new ResponseStatusException(NOT_FOUND))`.164165### Testing (Ch 4)16616715. **Match test slice to the layer being tested** (Ch 4):168 - Web layer only → `@WebMvcTest(MyController.class)` + `MockMvc`169 - Repository only → `@DataJpaTest`170 - Full app → `@SpringBootTest`171 - External service → `@MockBean` to replace17217316. **Use `MockMvc` for controller assertions without starting a server** (Ch 4):174 ```java175 mockMvc.perform(get("/books/1"))176 .andExpect(status().isOk())177 .andExpect(jsonPath("$.title").value("Spring Boot in Action"));178 ```17918017. **Use `@MockBean` to isolate the unit under test** (Ch 4). Replaces the real bean in the Spring context with a Mockito mock — cleaner than manual wiring.18118218. **Test security explicitly** (Ch 4). Use `.with(user("admin").roles("ADMIN"))` or `@WithMockUser` to assert secured endpoints reject unauthenticated requests.183184### Actuator (Ch 7)18518619. **Enable Actuator in every production app** (Ch 7). Add `spring-boot-starter-actuator`. At minimum expose `health` and `info`.18718820. **Write custom `HealthIndicator` for critical dependencies** (Ch 7):189 ```java190 @Component191 public class DatabaseHealthIndicator implements HealthIndicator {192 @Override193 public Health health() {194 return canConnect() ? Health.up().build()195 : Health.down().withDetail("reason", "timeout").build();196 }197 }198 ```19920021. **Add custom metrics via `MeterRegistry`** (Ch 7). Counter, gauge, timer — gives Prometheus/Grafana visibility into business events.20120222. **Restrict Actuator exposure in production** (Ch 7):203 ```properties204 management.endpoints.web.exposure.include=health,info205 management.endpoint.health.show-details=when-authorized206 ```207208### Deployment (Ch 8)20921023. **Package as an executable JAR by default** (Ch 8). `mvn package` produces a fat JAR with embedded Tomcat. Run with `java -jar app.jar`. No application server needed.21121224. **Create a production profile** (Ch 8). `application-production.properties` sets `spring.datasource.url`, disables dev tools, sets log levels to WARN.21321425. **Use Flyway or Liquibase for database migrations** (Ch 8). Add `spring-boot-starter-flyway`; place scripts in `classpath:db/migration/V1__init.sql`. Never use `spring.jpa.hibernate.ddl-auto=create` in production.215216---217218## Starter Cheat Sheet (Ch 2, Appendix B)219220| Need | Starter |221|------|---------|222| REST API | `spring-boot-starter-web` |223| JPA / Hibernate | `spring-boot-starter-data-jpa` |224| Security | `spring-boot-starter-security` |225| Observability | `spring-boot-starter-actuator` |226| Testing | `spring-boot-starter-test` |227| Thymeleaf views | `spring-boot-starter-thymeleaf` |228| Redis cache | `spring-boot-starter-data-redis` |229| Messaging | `spring-boot-starter-amqp` |230| DB migration | `flyway-core` |231232---233234## Code Structure Template235236```java237// Main class (Ch 2)238@SpringBootApplication239public class LibraryApp {240 public static void main(String[] args) {241 SpringApplication.run(LibraryApp.class, args);242 }243}244245// Entity (Ch 2)246@Entity247public class Book {248 @Id @GeneratedValue(strategy = GenerationType.IDENTITY)249 private Long id;250 private String title;251 private String isbn;252 // constructors, getters, setters253}254255// Repository (Ch 2)256public interface BookRepository extends JpaRepository<Book, Long> {257 List<Book> findByTitleContainingIgnoreCase(String title);258}259260// Service (Ch 2) — constructor injection261@Service262public class BookService {263 private final BookRepository repo;264 public BookService(BookRepository repo) { this.repo = repo; }265266 public Book findById(Long id) {267 return repo.findById(id)268 .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));269 }270}271272// Controller (Ch 2)273@RestController274@RequestMapping("/api/books")275public class BookController {276 private final BookService service;277 public BookController(BookService service) { this.service = service; }278279 @GetMapping("/{id}")280 public ResponseEntity<Book> getBook(@PathVariable Long id) {281 return ResponseEntity.ok(service.findById(id));282 }283284 @PostMapping285 public ResponseEntity<Book> createBook(@RequestBody Book book) {286 Book saved = service.save(book);287 URI location = URI.create("/api/books/" + saved.getId());288 return ResponseEntity.created(location).body(saved);289 }290}291292// application.properties (Ch 3)293// spring.datasource.url=jdbc:postgresql://localhost/library294// spring.datasource.username=${DB_USER}295// spring.datasource.password=${DB_PASS}296// spring.jpa.hibernate.ddl-auto=validate297// management.endpoints.web.exposure.include=health,info298299// application-dev.properties (Ch 3)300// spring.datasource.url=jdbc:h2:mem:library301// spring.jpa.hibernate.ddl-auto=create-drop302// logging.level.org.springframework=DEBUG303```304305---306307## Priority of Practices by Impact308309### Critical (Security & Correctness)310- Ch 3: Never hardcode credentials — use `${ENV_VAR}` in properties311- Ch 3: Secure Actuator endpoints — `env`, `beans`, `shutdown` must require auth312- Ch 4: Test secured endpoints explicitly — assert 401/403 on unauthenticated requests313- Ch 8: Never use `ddl-auto=create` in production — use Flyway/Liquibase314315### Important (Idiom & Maintainability)316- Ch 2: Constructor injection over `@Autowired` field injection317- Ch 2: `@RestController` over `@Controller` + `@ResponseBody` for APIs318- Ch 2: `Optional` from repository, never `null`319- Ch 3: `@ConfigurationProperties` over scattered `@Value` for grouped config320- Ch 3: Profiles for environment differences — not `if` statements321- Ch 4: `@WebMvcTest` for controller tests — not full `@SpringBootTest`322- Ch 7: Custom `HealthIndicator` for each critical dependency323324### Suggestions (Polish)325- Ch 3: Custom error pages in `templates/error/` — no code needed326- Ch 7: Custom metrics via `MeterRegistry` for business events327- Ch 8: Production profile disables dev tools, sets WARN log level328- Ch 2: Use `spring-boot-devtools` in dev for live reload