spring-boot4-skill 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,50 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.2.0] - 2026-02-21
9
+
10
+ ### Added
11
+
12
+ - **Docs:** CHANGELOG.md (Keep a Changelog); README "What you get" example tree, expanded Contributing, npm version badge.
13
+ - **CLI:** Project `description` in wizard and in generated Application.java, build.gradle.kts, pom.xml; friendly error messages (EEXIST, EACCES); "Minimal (API only)" option in wizard.
14
+ - **Skill:** New reference `spring-messaging.md` (Kafka, @KafkaListener, producer/consumer); SKILL.md Quick decision node for Messaging/Kafka; Health groups (readiness/liveness) in spring-boot-4.md; BOM sync note in build-templates.md.
15
+ - **Quality:** Jest config and unit tests for templates and buildContext; `scripts/smoke-cli.sh` and `npm run smoke`; `buildContext` exported for tests.
16
+
17
+ ### Changed
18
+
19
+ - **CLI:** Generator context includes `description` default; success message already showed absolute path.
20
+
21
+ [1.2.0]: https://github.com/AyrtonAldayr/agent-skill-java-spring-framework/compare/v1.1.0...v1.2.0
22
+
23
+ ## [1.1.0] - 2026-02-21
24
+
25
+ ### Added
26
+
27
+ - **Skill:** New reference `spring-security-7.md` — OAuth2 Resource Server, JWT, method security (`@PreAuthorize`), CORS.
28
+ - **Skill:** New reference `troubleshooting-migration.md` — common errors (javax/jakarta, RestTemplate, JSpecify, native), Boot 3→4 migration checklist.
29
+ - **Skill:** "When NOT to use this skill" section and Quick decision (mermaid) in SKILL.md.
30
+ - **Skill:** Spring Boot 4 reference: Testcontainers subsection, secure Actuator exposure, Reactive stack (R2DBC + WebFlux), redirect to spring-security-7.md for OAuth2/JWT.
31
+ - **Skill:** Spring Modulith reference: "Common pitfalls" subsection.
32
+ - **Skill:** Spring Framework 7: note for reactive apps (WebFlux + R2DBC) with link to spring-boot-4.md.
33
+ - **README:** Install options for skill via `npx skills add` and `claude skills install`; skill reference table updated.
34
+
35
+ ### Changed
36
+
37
+ - **Skill:** Reference table in SKILL.md now includes Spring Security 7 and Troubleshooting & migration; triggers listed in body.
38
+
39
+ ## [1.0.0] - 2026-02-21
40
+
41
+ ### Added
42
+
43
+ - **CLI:** Interactive wizard for project name, package, build tool (Gradle KTS / Maven), Java version (25/21/17), Spring Boot version, database (PostgreSQL, MySQL, MongoDB, H2, None), features (Actuator, Security, Validation, Modulith, Native, WebFlux, Docker Compose), and Java preview features.
44
+ - **CLI:** Non-interactive mode (`--no-interactive`) and Maven option (`--maven`).
45
+ - **Templates:** Gradle Kotlin DSL and Maven POM with Spring Boot 4.0.3 BOM; Application.java with JSpecify `@NullMarked`, application.yaml with virtual threads and OTEL, ApplicationTests, optional compose.yaml and Modulith skeleton.
46
+ - **Skill:** Core SKILL.md with mandatory workflow (Analyze → Implement → Optimize → Document), Core Principles table, and reference files (spring-framework-7, spring-boot-4, spring-modulith, build-templates).
47
+ - **npm:** Package `spring-boot4-skill` publishable with `npx spring-boot4-skill`.
48
+
49
+ [1.1.0]: https://github.com/AyrtonAldayr/agent-skill-java-spring-framework/compare/v1.0.0...v1.1.0
50
+ [1.0.0]: https://github.com/AyrtonAldayr/agent-skill-java-spring-framework/releases/tag/v1.0.0
package/README.md CHANGED
@@ -1,7 +1,9 @@
1
1
  # Spring Boot 4 · Java 25 · Spring Framework 7
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/spring-boot4-skill.svg)](https://www.npmjs.com/package/spring-boot4-skill)
4
+
3
5
  > **2026-standard** project scaffolder + Claude Code AI skill for Java / Spring development.
4
- > By [AyrtonAldayr](https://github.com/AyrtonAldayr)
6
+ > By [AyrtonAldayr](https://github.com/AyrtonAldayr) · **v1.2.0**
5
7
 
6
8
  This repository provides two tools in one:
7
9
 
@@ -44,6 +46,26 @@ After answering a few prompts, you get a complete project with:
44
46
  - **`compose.yaml`** *(optional)* — Docker Compose for PostgreSQL 17, MongoDB 7, OTEL Collector
45
47
  - **Spring Modulith module skeleton** *(optional)*
46
48
 
49
+ **Example output** (Gradle, default options):
50
+
51
+ ```
52
+ my-service/
53
+ ├── build.gradle.kts
54
+ ├── settings.gradle.kts
55
+ ├── src/
56
+ │ ├── main/
57
+ │ │ ├── java/<package-path>/
58
+ │ │ │ └── Application.java
59
+ │ │ └── resources/
60
+ │ │ └── application.yaml
61
+ │ └── test/
62
+ │ └── java/<package-path>/
63
+ │ └── ApplicationTests.java
64
+ └── compose.yaml # if Docker Compose was selected
65
+ ```
66
+
67
+ The generator prints the full path of the created project (e.g. `Project created at /path/to/my-service`).
68
+
47
69
  ### Wizard options
48
70
 
49
71
  ```
@@ -70,7 +92,26 @@ After answering a few prompts, you get a complete project with:
70
92
 
71
93
  Install the skill to get AI assistance aligned with Spring Boot 4 / Framework 7 standards.
72
94
 
73
- ### Install from GitHub
95
+ There are **two common ways** to install it, depending on which tool you use:
96
+
97
+ ### Option 1 — npx (Skills CLI, multi-agent)
98
+
99
+ Uses the open [Skills CLI](https://github.com/vercel-labs/skills) (`npx skills`). Works with Claude Code, Cursor, Codex, and other agents that follow the same skill layout.
100
+
101
+ ```bash
102
+ # Install the skill (prompts for which agent(s) to install to)
103
+ npx skills add AyrtonAldayr/agent-skill-java-spring-framework --skill java-spring-framework
104
+
105
+ # Install only for Claude Code
106
+ npx skills add AyrtonAldayr/agent-skill-java-spring-framework --skill java-spring-framework -a claude-code
107
+
108
+ # Install for Cursor
109
+ npx skills add AyrtonAldayr/agent-skill-java-spring-framework --skill java-spring-framework -a cursor
110
+ ```
111
+
112
+ ### Option 2 — Claude Code official CLI
113
+
114
+ If you use the `claude` CLI (Anthropic’s Claude Code), you can install directly from GitHub:
74
115
 
75
116
  ```bash
76
117
  claude skills install github:AyrtonAldayr/agent-skill-java-spring-framework
@@ -93,9 +134,12 @@ Once installed, Claude Code acts as a **Senior Spring Boot 4 architect**:
93
134
  | File | Contents |
94
135
  |---|---|
95
136
  | `skills/java-spring-framework/references/spring-framework-7.md` | All Spring 7 APIs with code examples |
96
- | `skills/java-spring-framework/references/spring-boot-4.md` | Boot 4 features: native, virtual threads, Security 7, testing |
97
- | `skills/java-spring-framework/references/spring-modulith.md` | Module structure, events, integration testing |
137
+ | `skills/java-spring-framework/references/spring-boot-4.md` | Boot 4: native, virtual threads, testing (Testcontainers), reactive stack |
138
+ | `skills/java-spring-framework/references/spring-security-7.md` | OAuth2 Resource Server, JWT, method security, CORS |
139
+ | `skills/java-spring-framework/references/spring-messaging.md` | Kafka, event-driven, @KafkaListener, producer/consumer |
140
+ | `skills/java-spring-framework/references/spring-modulith.md` | Module structure, events, integration testing, common pitfalls |
98
141
  | `skills/java-spring-framework/references/build-templates.md` | Complete Gradle KTS + Maven POM templates |
142
+ | `skills/java-spring-framework/references/troubleshooting-migration.md` | Common errors (javax/jakarta, RestTemplate), Boot 3→4 checklist |
99
143
 
100
144
  ---
101
145
 
@@ -130,12 +174,17 @@ npx spring-boot4-skill
130
174
 
131
175
  ## Contributing
132
176
 
133
- PRs welcome. Please keep all generated code aligned with:
134
-
135
- - [Spring Boot 4.x docs](https://docs.spring.io/spring-boot/)
136
- - [Spring Framework 7.x docs](https://docs.spring.io/spring-framework/reference/)
137
- - [Spring Modulith docs](https://docs.spring.io/spring-modulith/reference/)
138
- - [JSpecify](https://jspecify.dev/)
177
+ PRs welcome.
178
+
179
+ 1. **Clone and install:** `git clone <repo> && cd agent-skill-java-spring-framework && npm install`
180
+ 2. **Run CLI locally:** `node bin/create-spring-app.js [project-name]` or `node bin/create-spring-app.js my-test --no-interactive`
181
+ 3. **Tests:** `npm test` (unit tests); `npm run smoke` (CLI smoke test).
182
+ 4. **Skill changes:** Keep the structure under `skills/java-spring-framework/` (SKILL.md plus `references/*.md`). Load criteria in the Reference Files table should stay accurate.
183
+ 5. **Generated code** must align with:
184
+ - [Spring Boot 4.x docs](https://docs.spring.io/spring-boot/)
185
+ - [Spring Framework 7.x docs](https://docs.spring.io/spring-framework/reference/)
186
+ - [Spring Modulith docs](https://docs.spring.io/spring-modulith/reference/)
187
+ - [JSpecify](https://jspecify.dev/)
139
188
 
140
189
  ---
141
190
 
@@ -0,0 +1,9 @@
1
+ /** @type {import('jest').Config} */
2
+ module.exports = {
3
+ testEnvironment: 'node',
4
+ testMatch: ['**/*.test.js'],
5
+ transform: {},
6
+ moduleNameMapper: {
7
+ '^(\\.{1,2}/.*)\\.js$': '$1',
8
+ },
9
+ };
package/lib/generator.js CHANGED
@@ -15,11 +15,9 @@ const TEMPLATES_DIR = join(__dirname, '../templates');
15
15
  export async function generateProject(config) {
16
16
  const spinner = ora('Generating project…').start();
17
17
  const projectDir = resolve(process.cwd(), config.projectName);
18
+ const ctx = buildContext(config);
18
19
 
19
20
  try {
20
- // Derive additional config values
21
- const ctx = buildContext(config);
22
-
23
21
  // Create root project directory
24
22
  mkdirSync(projectDir, { recursive: true });
25
23
 
@@ -45,15 +43,21 @@ export async function generateProject(config) {
45
43
 
46
44
  } catch (err) {
47
45
  spinner.fail('Generation failed');
46
+ const msg = err.code === 'EEXIST'
47
+ ? `Directory already exists: ${projectDir}`
48
+ : err.code === 'EACCES'
49
+ ? `Permission denied writing to: ${projectDir}`
50
+ : err.message || String(err);
51
+ console.error(chalk.red(msg));
48
52
  throw err;
49
53
  }
50
54
  }
51
55
 
52
56
  // ---------------------------------------------------------------------------
53
- // Context builder
57
+ // Context builder (exported for tests)
54
58
  // ---------------------------------------------------------------------------
55
59
 
56
- function buildContext(config) {
60
+ export function buildContext(config) {
57
61
  const appName = toPascalCase(config.projectName);
58
62
  const packagePath = config.packageName.replace(/\./g, '/');
59
63
  const features = config.features || [];
@@ -76,6 +80,7 @@ function buildContext(config) {
76
80
 
77
81
  return {
78
82
  ...config,
83
+ description: config.description ?? 'Spring Boot 4 microservice',
79
84
  appName,
80
85
  packagePath,
81
86
  features,
@@ -179,9 +184,11 @@ ${chalk.bold('Next steps:')}
179
184
  ${chalk.cyan(cd)}
180
185
  ${chalk.cyan(run)}
181
186
 
182
- ${chalk.bold('Install the AI coding skill:')}
187
+ ${chalk.bold('Install the AI coding skill (optional):')}
183
188
 
184
- ${chalk.cyan(`claude skills install github:AyrtonAldayr/agent-skill-java-spring-framework`)}
189
+ ${chalk.cyan('npx skills add AyrtonAldayr/agent-skill-java-spring-framework --skill java-spring-framework')}
190
+ ${chalk.dim('or')}
191
+ ${chalk.cyan('claude skills install github:AyrtonAldayr/agent-skill-java-spring-framework')}
185
192
 
186
193
  ${chalk.dim('Docs: https://github.com/AyrtonAldayr/agent-skill-java-spring-framework')}
187
194
  `);
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Unit tests for the generator context builder (buildContext).
3
+ */
4
+
5
+ import { buildContext } from './generator.js';
6
+
7
+ describe('buildContext', () => {
8
+ it('includes projectName, packageName, appName, packagePath', () => {
9
+ const ctx = buildContext({
10
+ projectName: 'my-service',
11
+ packageName: 'com.acme',
12
+ database: 'none',
13
+ features: [],
14
+ });
15
+ expect(ctx.projectName).toBe('my-service');
16
+ expect(ctx.packageName).toBe('com.acme');
17
+ expect(ctx.appName).toBe('MyService');
18
+ expect(ctx.packagePath).toBe('com/acme');
19
+ });
20
+
21
+ it('sets hasJpa true for postgresql, mysql, h2', () => {
22
+ expect(buildContext({ projectName: 'x', packageName: 'c', database: 'postgresql', features: [] }).hasJpa).toBe(true);
23
+ expect(buildContext({ projectName: 'x', packageName: 'c', database: 'mysql', features: [] }).hasJpa).toBe(true);
24
+ expect(buildContext({ projectName: 'x', packageName: 'c', database: 'h2', features: [] }).hasJpa).toBe(true);
25
+ expect(buildContext({ projectName: 'x', packageName: 'c', database: 'none', features: [] }).hasJpa).toBe(false);
26
+ });
27
+
28
+ it('sets hasActuator, hasSecurity, hasValidation from features', () => {
29
+ const ctx = buildContext({
30
+ projectName: 'x',
31
+ packageName: 'c',
32
+ database: 'none',
33
+ features: ['actuator', 'validation'],
34
+ });
35
+ expect(ctx.hasActuator).toBe(true);
36
+ expect(ctx.hasSecurity).toBe(false);
37
+ expect(ctx.hasValidation).toBe(true);
38
+ });
39
+
40
+ it('sets hasModulith, hasNative, hasDockerCompose from features', () => {
41
+ const ctx = buildContext({
42
+ projectName: 'x',
43
+ packageName: 'c',
44
+ database: 'postgresql',
45
+ features: ['modulith', 'docker-compose'],
46
+ });
47
+ expect(ctx.hasModulith).toBe(true);
48
+ expect(ctx.hasNative).toBe(false);
49
+ expect(ctx.hasDockerCompose).toBe(true);
50
+ });
51
+
52
+ it('provides description default when missing', () => {
53
+ const ctx = buildContext({
54
+ projectName: 'x',
55
+ packageName: 'c',
56
+ database: 'none',
57
+ features: [],
58
+ });
59
+ expect(ctx.description).toBe('Spring Boot 4 microservice');
60
+ });
61
+
62
+ it('uses provided description when set', () => {
63
+ const ctx = buildContext({
64
+ projectName: 'x',
65
+ packageName: 'c',
66
+ description: 'My API',
67
+ database: 'none',
68
+ features: [],
69
+ });
70
+ expect(ctx.description).toBe('My API');
71
+ });
72
+
73
+ it('sets modulithDataDep for JPA when modulith and postgresql', () => {
74
+ const ctx = buildContext({
75
+ projectName: 'x',
76
+ packageName: 'c',
77
+ database: 'postgresql',
78
+ features: ['modulith'],
79
+ });
80
+ expect(ctx.modulithDataDep).toContain('modulith-starter-jpa');
81
+ });
82
+ });
package/lib/prompts.js CHANGED
@@ -7,6 +7,7 @@ import inquirer from 'inquirer';
7
7
  const DEFAULTS = {
8
8
  projectName: 'my-service',
9
9
  packageName: 'com.example',
10
+ description: 'Spring Boot 4 microservice',
10
11
  buildTool: 'gradle',
11
12
  javaVersion: '25',
12
13
  bootVersion: '4.0.3',
@@ -88,6 +89,12 @@ export async function runWizard(projectNameArg, options = {}) {
88
89
  ],
89
90
  default: 'postgresql',
90
91
  },
92
+ {
93
+ type: 'confirm',
94
+ name: 'minimal',
95
+ message: 'Minimal project (API only — no Actuator, Security, Validation)?',
96
+ default: false,
97
+ },
91
98
  {
92
99
  type: 'checkbox',
93
100
  name: 'features',
@@ -101,6 +108,7 @@ export async function runWizard(projectNameArg, options = {}) {
101
108
  { name: 'Spring WebFlux (Reactive)', value: 'webflux', checked: false },
102
109
  { name: 'Docker Compose support', value: 'docker-compose', checked: false },
103
110
  ],
111
+ when: (ans) => !ans.minimal,
104
112
  },
105
113
  {
106
114
  type: 'confirm',
@@ -111,5 +119,8 @@ export async function runWizard(projectNameArg, options = {}) {
111
119
  },
112
120
  ]);
113
121
 
122
+ if (answers.minimal) {
123
+ answers.features = [];
124
+ }
114
125
  return answers;
115
126
  }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Unit tests for the template engine (renderTemplate, interpolate, #if, #each).
3
+ * Uses in-memory template strings; does not read from disk.
4
+ */
5
+
6
+ import { readFileSync, writeFileSync, mkdtempSync, rmSync } from 'fs';
7
+ import { join } from 'path';
8
+ import { tmpdir } from 'os';
9
+ import { renderTemplate } from './templates.js';
10
+
11
+ describe('templates', () => {
12
+ let tempDir;
13
+
14
+ beforeEach(() => {
15
+ tempDir = mkdtempSync(join(tmpdir(), 'skill-tpl-'));
16
+ });
17
+
18
+ afterEach(() => {
19
+ rmSync(tempDir, { recursive: true });
20
+ });
21
+
22
+ function createTemplate(content) {
23
+ const path = join(tempDir, 'tpl.txt');
24
+ writeFileSync(path, content);
25
+ return path;
26
+ }
27
+
28
+ describe('interpolate', () => {
29
+ it('replaces {{key}} with context value', () => {
30
+ const path = createTemplate('Hello {{name}}');
31
+ expect(renderTemplate(path, { name: 'World' })).toBe('Hello World');
32
+ });
33
+
34
+ it('replaces multiple placeholders', () => {
35
+ const path = createTemplate('{{a}} and {{b}}');
36
+ expect(renderTemplate(path, { a: 'A', b: 'B' })).toBe('A and B');
37
+ });
38
+
39
+ it('replaces undefined/null with empty string', () => {
40
+ const path = createTemplate('x{{missing}}y');
41
+ expect(renderTemplate(path, {})).toBe('xy');
42
+ });
43
+ });
44
+
45
+ describe('{{#if}}', () => {
46
+ it('includes block when value is truthy', () => {
47
+ const path = createTemplate('{{#if ok}}yes{{/if}}');
48
+ expect(renderTemplate(path, { ok: true })).toBe('yes');
49
+ });
50
+
51
+ it('omits block when value is falsy', () => {
52
+ const path = createTemplate('{{#if ok}}yes{{/if}}');
53
+ expect(renderTemplate(path, { ok: false })).toBe('');
54
+ });
55
+
56
+ it('supports negation {{#if !key}}', () => {
57
+ const path = createTemplate('{{#if !hide}}show{{/if}}');
58
+ expect(renderTemplate(path, { hide: true })).toBe('');
59
+ expect(renderTemplate(path, { hide: false })).toBe('show');
60
+ });
61
+ });
62
+
63
+ describe('{{#each}}', () => {
64
+ it('iterates array and exposes {{item}}', () => {
65
+ const path = createTemplate('{{#each items}}({{item}}){{/each}}');
66
+ expect(renderTemplate(path, { items: ['a', 'b'] })).toBe('(a)(b)');
67
+ });
68
+
69
+ it('returns empty string when key is not an array', () => {
70
+ const path = createTemplate('{{#each items}}x{{/each}}');
71
+ expect(renderTemplate(path, { items: null })).toBe('');
72
+ });
73
+ });
74
+
75
+ it('combines if and interpolate', () => {
76
+ const path = createTemplate('{{name}}{{#if active}} (active){{/if}}');
77
+ expect(renderTemplate(path, { name: 'Foo', active: true })).toBe('Foo (active)');
78
+ expect(renderTemplate(path, { name: 'Bar', active: false })).toBe('Bar');
79
+ });
80
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spring-boot4-skill",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Interactive CLI scaffold for Java 25 / Spring Boot 4.x projects — with a bundled Claude Code skill for AI-assisted development.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +8,8 @@
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node bin/create-spring-app.js",
11
- "test": "node --experimental-vm-modules node_modules/.bin/jest"
11
+ "test": "node --experimental-vm-modules node_modules/.bin/jest",
12
+ "smoke": "bash scripts/smoke-cli.sh"
12
13
  },
13
14
  "keywords": [
14
15
  "spring-boot",
@@ -40,5 +41,8 @@
40
41
  "commander": "^12.1.0",
41
42
  "inquirer": "^10.1.5",
42
43
  "ora": "^8.1.0"
44
+ },
45
+ "devDependencies": {
46
+ "jest": "^29.7.0"
43
47
  }
44
48
  }
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ # Smoke test: run CLI in non-interactive mode and verify key files exist.
3
+ set -e
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
7
+ CLI="$REPO_ROOT/bin/create-spring-app.js"
8
+ PROJECT_NAME="smoke-test-project"
9
+ TMP_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'skill-smoke')
10
+
11
+ cd "$TMP_DIR"
12
+ node "$CLI" "$PROJECT_NAME" --no-interactive
13
+
14
+ PROJECT_DIR="$TMP_DIR/$PROJECT_NAME"
15
+ if [ ! -d "$PROJECT_DIR" ]; then
16
+ echo "FAIL: Project directory not created: $PROJECT_DIR"
17
+ exit 1
18
+ fi
19
+
20
+ if [ ! -f "$PROJECT_DIR/build.gradle.kts" ]; then
21
+ echo "FAIL: build.gradle.kts not found"
22
+ exit 1
23
+ fi
24
+
25
+ if [ ! -f "$PROJECT_DIR/src/main/resources/application.yaml" ]; then
26
+ echo "FAIL: application.yaml not found"
27
+ exit 1
28
+ fi
29
+
30
+ # Default package is com.example; app name from "smoke-test-project" -> SmokeTestProject
31
+ JAVA_FILE=$(find "$PROJECT_DIR/src/main/java" -name "*Application.java" 2>/dev/null | head -1)
32
+ if [ -z "$JAVA_FILE" ] || [ ! -f "$JAVA_FILE" ]; then
33
+ echo "FAIL: Application main class not found under src/main/java"
34
+ exit 1
35
+ fi
36
+
37
+ echo "PASS: Smoke test — CLI generated project with build.gradle.kts, application.yaml, and Application class."
38
+ rm -rf "$TMP_DIR"
@@ -16,6 +16,34 @@ description: >
16
16
  You are a Senior Java & Spring Boot 4 / Spring Framework 7 architect. All code must be
17
17
  idiomatic for **2026 standards**: Spring Boot 4.0.x, Spring Framework 7.0.x, Java 25, Jakarta EE 11.
18
18
 
19
+ **Triggers:** REST APIs, microservices, JdbcClient/JPA 3.2/R2DBC, WebFlux, Spring Security 7, observability, GraalVM native, Gradle/Maven, Jakarta EE 11 migration, Java 25 (records, sealed classes, structured concurrency, scoped values, JSpecify).
20
+
21
+ ## When NOT to use this skill
22
+
23
+ - Legacy Spring Boot 2.x or 3.x with no upgrade plan to Boot 4 / Framework 7.
24
+ - Non-Spring JVM stacks (Quarkus, Micronaut, Helidon) unless the user explicitly asks for Spring comparison or migration.
25
+ - Tasks that do not touch Java/Spring backend (e.g. only frontend, only infra/DevOps with no Spring code).
26
+ - General Java questions with no Spring or framework context.
27
+
28
+ ## Quick decision (which reference to load first)
29
+
30
+ ```mermaid
31
+ flowchart TD
32
+ A[User request] --> B{REST blocking or reactive?}
33
+ B -->|Blocking MVC + JDBC/JPA| C[spring-boot-4.md + spring-framework-7.md]
34
+ B -->|Reactive WebFlux + R2DBC| D[spring-boot-4.md Reactive section + spring-framework-7.md]
35
+ A --> E{Modular monolith?}
36
+ E -->|Yes| F[spring-modulith.md]
37
+ A --> G{Security / OAuth2 / JWT?}
38
+ G -->|Yes| H[spring-security-7.md]
39
+ A --> I{Scaffold / build / versions?}
40
+ I -->|Yes| J[build-templates.md]
41
+ A --> K{Migration or errors?}
42
+ K -->|Yes| L[troubleshooting-migration.md]
43
+ A --> M{Messaging / Kafka?}
44
+ M -->|Yes| N[spring-messaging.md]
45
+ ```
46
+
19
47
  ## Mandatory Workflow
20
48
 
21
49
  1. **Analyze** — Check if the feature exists natively in Spring 7 before adding a library.
@@ -52,5 +80,8 @@ Load these as needed — do not load all at once:
52
80
  |---|---|---|
53
81
  | Spring Framework 7 APIs | `references/spring-framework-7.md` | Framework-level features: versioning, resilience, JSpecify, SpEL, streaming |
54
82
  | Spring Boot 4 features | `references/spring-boot-4.md` | Boot auto-config, Actuator, native images, testing, virtual threads |
83
+ | Spring Security 7 | `references/spring-security-7.md` | OAuth2 Resource Server, JWT, method security, CORS, authentication/authorization |
84
+ | Messaging (Kafka) | `references/spring-messaging.md` | Kafka, event-driven, messaging, @KafkaListener, producer/consumer |
55
85
  | Spring Modulith | `references/spring-modulith.md` | Domain-driven module design, event-driven architecture |
56
86
  | Build templates | `references/build-templates.md` | Gradle KTS or Maven POM scaffolding with 2026 BOM versions |
87
+ | Troubleshooting & migration | `references/troubleshooting-migration.md` | Migration from Boot 3, compile/runtime errors (javax/jakarta, RestTemplate, native, null-safety) |
@@ -276,6 +276,8 @@ dependencies {
276
276
  | Micrometer | 1.15.x |
277
277
  | io.spring.dependency-management | 1.1.7 |
278
278
 
279
+ When updating BOM versions, keep the CLI templates under `templates/` and this reference in sync.
280
+
279
281
  ---
280
282
 
281
283
  ## 4. Spring Initializr CLI Quick Start
@@ -15,6 +15,7 @@
15
15
  8. [Scoped Values (Java 25)](#8-scoped-values-java-25)
16
16
  9. [Docker Compose Support](#9-docker-compose-support)
17
17
  10. [Spring Security 7 Basics](#10-spring-security-7-basics)
18
+ 11. [Reactive Stack (R2DBC + WebFlux)](#11-reactive-stack-r2dbc--webflux)
18
19
 
19
20
  ---
20
21
 
@@ -126,19 +127,67 @@ class UserRepositoryTest {
126
127
  }
127
128
  ```
128
129
 
130
+ ### Testcontainers (integration tests)
131
+
132
+ For full integration tests against a real database, use **Testcontainers**. Prefer this when testing repository logic, transactions, or schema; use `@MockBean` for unit/slice tests where the focus is the controller or service in isolation.
133
+
134
+ ```kotlin
135
+ // build.gradle.kts
136
+ testImplementation("org.springframework.boot:spring-boot-testcontainers")
137
+ testImplementation("org.testcontainers:postgresql")
138
+ testImplementation("org.testcontainers:junit-jupiter")
139
+ ```
140
+
141
+ ```java
142
+ @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
143
+ @Testcontainers
144
+ class OrderRepositoryIntegrationTest {
145
+
146
+ @Container
147
+ static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:17")
148
+ .withDatabaseName("testdb").withUsername("test").withPassword("test");
149
+
150
+ @DynamicPropertySource
151
+ static void props(DynamicPropertyRegistry registry) {
152
+ registry.add("spring.datasource.url", postgres::getJdbcUrl);
153
+ registry.add("spring.datasource.username", postgres::getUsername);
154
+ registry.add("spring.datasource.password", postgres::getPassword);
155
+ }
156
+
157
+ @Autowired OrderRepository repository;
158
+
159
+ @Test
160
+ void shouldFindOrdersByUserId() {
161
+ // real DB; no mocks
162
+ List<Order> orders = repository.findByUserId("user-1");
163
+ assertThat(orders).isNotEmpty();
164
+ }
165
+ }
166
+ ```
167
+
168
+ **When to use @MockBean vs Testcontainers:** Use `@MockBean` in `@WebMvcTest` or `@DataJpaTest` when you want fast, isolated tests. Use Testcontainers when you need real DB/network behavior (transactions, constraints, SQL).
169
+
129
170
  ---
130
171
 
131
172
  ## 4. Actuator & Observability
132
173
 
133
174
  Spring Boot 4 ships first-class **Micrometer Tracing + OpenTelemetry** support.
134
175
 
176
+ ### Endpoint exposure (secure by default)
177
+
178
+ Expose only what you need. In production, prefer `health` and `info` for public checks; restrict `metrics`, `prometheus`, and `traces` to an internal network or secure endpoint (e.g. via Spring Security).
179
+
135
180
  ```yaml
136
181
  # application.yaml
137
182
  management:
138
183
  endpoints:
139
184
  web:
140
185
  exposure:
141
- include: health,info,metrics,prometheus,traces
186
+ include: health,info,metrics,prometheus,traces # restrict in prod
187
+ base-path: /actuator
188
+ endpoint:
189
+ health:
190
+ show-details: when-authorized # or "never" in prod
142
191
  tracing:
143
192
  sampling:
144
193
  probability: 1.0
@@ -150,13 +199,42 @@ management:
150
199
  endpoint: http://otel-collector:4318/v1/traces
151
200
  ```
152
201
 
202
+ Protect actuator in Security 7: allow `health`/`info` for load balancers, require authentication for `metrics`/`prometheus`/`traces`. See `references/spring-security-7.md`.
203
+
204
+ ### Health groups (readiness / liveness)
205
+
206
+ Customize health groups for Kubernetes or load balancers. Example: a custom "readiness" group that includes DB and a custom indicator:
207
+
208
+ ```yaml
209
+ management:
210
+ endpoint:
211
+ health:
212
+ show-details: when-authorized
213
+ health:
214
+ livenessstate:
215
+ enabled: true
216
+ readinessstate:
217
+ enabled: true
218
+ db:
219
+ enabled: true
220
+ group:
221
+ readiness:
222
+ include: readinessState,db
223
+ liveness:
224
+ include: livenessState
225
+ ```
226
+
227
+ Expose only the group endpoints (e.g. `/actuator/health/readiness`, `/actuator/health/liveness`) in Security 7 for k8s probes.
228
+
229
+ ### Dependencies (Micrometer + OTEL)
230
+
153
231
  ```kotlin
154
232
  // build.gradle.kts
155
233
  implementation("io.micrometer:micrometer-tracing-bridge-otel")
156
234
  implementation("io.opentelemetry:opentelemetry-exporter-otlp")
157
235
  ```
158
236
 
159
- Custom span:
237
+ ### Custom span (Tracer)
160
238
 
161
239
  ```java
162
240
  @Service
@@ -318,7 +396,7 @@ No manual connection properties needed — Boot auto-configures `DataSource` and
318
396
 
319
397
  ## 10. Spring Security 7 Basics
320
398
 
321
- Lambda DSL is the only supported style in Spring Security 7.
399
+ Spring Security 7 uses **lambda DSL** only for `SecurityFilterChain` and related config. Minimal example:
322
400
 
323
401
  ```java
324
402
  @Configuration
@@ -332,10 +410,8 @@ public class SecurityConfig {
332
410
  .requestMatchers("/api/public/**").permitAll()
333
411
  .requestMatchers("/api/admin/**").hasRole("ADMIN")
334
412
  .anyRequest().authenticated())
335
- .oauth2ResourceServer(oauth2 -> oauth2
336
- .jwt(jwt -> jwt.decoder(jwtDecoder())))
337
- .sessionManagement(session -> session
338
- .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
413
+ .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.decoder(jwtDecoder())))
414
+ .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
339
415
  .build();
340
416
  }
341
417
 
@@ -345,3 +421,28 @@ public class SecurityConfig {
345
421
  }
346
422
  }
347
423
  ```
424
+
425
+ For **OAuth2 Resource Server (JWT)**, **method security (`@PreAuthorize`)**, and **CORS**, load `references/spring-security-7.md`.
426
+
427
+ ---
428
+
429
+ ## 11. Reactive Stack (R2DBC + WebFlux)
430
+
431
+ Choose the reactive stack when you need non-blocking I/O end-to-end (high concurrency, streaming, or integration with reactive drivers). For typical CRUD APIs with blocking JDBC/JPA, prefer **Spring MVC + virtual threads** (section 6).
432
+
433
+ ### Dependencies
434
+
435
+ ```kotlin
436
+ // build.gradle.kts — replace or add to starters
437
+ implementation("org.springframework.boot:spring-boot-starter-webflux")
438
+ implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
439
+ runtimeOnly("org.postgresql:r2dbc-postgresql")
440
+ ```
441
+
442
+ ### Main application
443
+
444
+ Use `Netty` as the default server (Boot chooses it when `spring-boot-starter-webflux` is on the classpath and `spring-boot-starter-web` is not).
445
+
446
+ ### R2DBC repositories and WebFlux controllers
447
+
448
+ Define reactive repositories (`ReactiveCrudRepository`) and inject them into `@RestController` or handler functions. Use `ServerWebExchange`, `Mono`, and `Flux` for reactive types. For **RestClient** in a reactive app, use the reactive variant and streaming; see `references/spring-framework-7.md` (Streaming Support) for alignment with Spring 7 APIs.
@@ -2,6 +2,8 @@
2
2
 
3
3
  **Version**: 7.0.5 (Stable) | **Java baseline**: 17 (25 recommended) | **Jakarta EE**: 11
4
4
 
5
+ For **reactive applications (WebFlux + R2DBC)**, combine this reference with the **Reactive stack** section in `references/spring-boot-4.md`.
6
+
5
7
  ---
6
8
 
7
9
  ## Table of Contents
@@ -0,0 +1,132 @@
1
+ # Spring Messaging (Kafka) — Boot 4
2
+
3
+ **Spring Boot**: 4.0.x | **Spring Kafka**: aligned with Boot BOM | **Jakarta EE**: 11
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Dependencies](#1-dependencies)
10
+ 2. [application.yaml](#2-applicationyaml)
11
+ 3. [Consumer — @KafkaListener](#3-consumer--kafkalistener)
12
+ 4. [Producer](#4-producer)
13
+ 5. [Records for payloads](#5-records-for-payloads)
14
+
15
+ ---
16
+
17
+ ## 1. Dependencies
18
+
19
+ Spring Boot 4 BOM manages Spring Kafka. Add the starter:
20
+
21
+ ```kotlin
22
+ // build.gradle.kts
23
+ dependencies {
24
+ implementation("org.springframework.kafka:spring-kafka")
25
+ // or explicitly:
26
+ // implementation("org.springframework.boot:spring-boot-starter-web") // or webflux
27
+ // implementation("org.springframework.kafka:spring-kafka")
28
+ }
29
+ ```
30
+
31
+ For JSON (de)serialization with Jackson 3 (Jakarta):
32
+
33
+ ```kotlin
34
+ implementation("org.springframework.kafka:spring-kafka")
35
+ // Jackson is provided by Boot; ensure jakarta.* for JSON
36
+ ```
37
+
38
+ ---
39
+
40
+ ## 2. application.yaml
41
+
42
+ ```yaml
43
+ spring:
44
+ kafka:
45
+ bootstrap-servers: localhost:9092
46
+ consumer:
47
+ group-id: my-app
48
+ auto-offset-reset: earliest
49
+ key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
50
+ value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
51
+ properties:
52
+ spring.json.trusted.packages: "*"
53
+ producer:
54
+ key-serializer: org.apache.kafka.common.serialization.StringSerializer
55
+ value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
56
+ ```
57
+
58
+ ---
59
+
60
+ ## 3. Consumer — @KafkaListener
61
+
62
+ Use Records for message payloads where possible. Listen on a topic and process with Jakarta and JSpecify where applicable.
63
+
64
+ ```java
65
+ import org.springframework.kafka.annotation.KafkaListener;
66
+ import org.springframework.kafka.support.KafkaHeaders;
67
+ import org.springframework.messaging.handler.annotation.Header;
68
+ import org.springframework.messaging.handler.annotation.Payload;
69
+ import org.springframework.stereotype.Component;
70
+
71
+ @Component
72
+ public class OrderEventsConsumer {
73
+
74
+ @KafkaListener(topics = "orders", groupId = "my-app")
75
+ public void onOrder(@Payload OrderEvent event,
76
+ @Header(KafkaHeaders.RECEIVED_KEY) String key) {
77
+ // process event (OrderEvent as record)
78
+ }
79
+ }
80
+ ```
81
+
82
+ With batch consumption:
83
+
84
+ ```java
85
+ @KafkaListener(topics = "orders", groupId = "my-app", containerFactory = "batchFactory")
86
+ public void onOrders(@Payload List<OrderEvent> events) {
87
+ events.forEach(this::process);
88
+ }
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 4. Producer
94
+
95
+ Inject `KafkaTemplate` and send records. Use `JsonSerializer` for value when configured in application.yaml.
96
+
97
+ ```java
98
+ import org.springframework.kafka.core.KafkaTemplate;
99
+ import org.springframework.stereotype.Service;
100
+
101
+ @Service
102
+ public class OrderEventsProducer {
103
+
104
+ private final KafkaTemplate<String, OrderEvent> kafkaTemplate;
105
+
106
+ public OrderEventsProducer(KafkaTemplate<String, OrderEvent> kafkaTemplate) {
107
+ this.kafkaTemplate = kafkaTemplate;
108
+ }
109
+
110
+ public void send(OrderEvent event) {
111
+ kafkaTemplate.send("orders", event.orderId(), event);
112
+ }
113
+ }
114
+ ```
115
+
116
+ For a simple `KafkaTemplate<String, Object>` with JSON, ensure your payload type is on the trusted packages list for the consumer’s `JsonDeserializer`.
117
+
118
+ ---
119
+
120
+ ## 5. Records for payloads
121
+
122
+ Prefer Java records for event DTOs (Jackson 3 supports them):
123
+
124
+ ```java
125
+ public record OrderEvent(String orderId, String productId, int quantity, java.time.Instant at) {}
126
+ ```
127
+
128
+ Use `jakarta.*` and JSpecify nullability in shared libraries if you need strict null contracts; for internal events, records are often sufficient.
129
+
130
+ ---
131
+
132
+ **Summary:** Use `spring-kafka` with Boot 4 BOM, configure bootstrap servers and (de)serializers in `application.yaml`, and use `@KafkaListener` for consumers and `KafkaTemplate` for producers. Prefer records for event payloads. For Spring Cloud Stream (bindings), see the Spring Cloud Stream docs; the same Kafka dependencies can be used with Stream if you add the appropriate starters.
@@ -13,6 +13,7 @@
13
13
  6. [Transactional Event Listeners](#6-transactional-event-listeners)
14
14
  7. [Module Integration Testing](#7-module-integration-testing)
15
15
  8. [Generating Documentation](#8-generating-documentation)
16
+ 9. [Common Pitfalls](#9-common-pitfalls)
16
17
 
17
18
  ---
18
19
 
@@ -218,3 +219,15 @@ void generateModuleDocumentation() throws Exception {
218
219
  ```
219
220
 
220
221
  Output lands in `target/spring-modulith-docs/` (Maven) or `build/spring-modulith-docs/` (Gradle).
222
+
223
+ ---
224
+
225
+ ## 9. Common Pitfalls
226
+
227
+ | Pitfall | Remedy |
228
+ |--------|--------|
229
+ | **Circular module dependencies** | `ApplicationModules.of(...).verify()` fails. Break the cycle: introduce a shared module or move the coupling to events so no module depends on the other. |
230
+ | **Exposing internal types in the module API** | Public classes in the module root (e.g. `OrderService`) must not return or accept types from `internal/`. Use DTOs or domain types in the public package only. |
231
+ | **Forgetting the verification test in CI** | Add the module verification test (section 3) to your CI pipeline so boundary violations are caught on every commit. |
232
+ | **Direct service calls across modules** | One module must not inject another module's service and call it directly. Use `ApplicationEventPublisher` and `@ApplicationModuleListener` (or transactional events) for cross-module communication. |
233
+ | **Putting shared DTOs in one module's internal** | Types used in events or APIs consumed by several modules belong in a `shared/` (or similar) package, not in one module's `internal/`. |
@@ -0,0 +1,203 @@
1
+ # Spring Security 7 — Reference (Boot 4)
2
+
3
+ **Spring Boot**: 4.0.x | **Spring Security**: 7.0.x | **Jakarta EE**: 11
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Dependencies](#1-dependencies)
10
+ 2. [OAuth2 Resource Server (JWT)](#2-oauth2-resource-server-jwt)
11
+ 3. [SecurityFilterChain (Lambda DSL)](#3-securityfilterchain-lambda-dsl)
12
+ 4. [Method Security (@PreAuthorize)](#4-method-security-preauthorize)
13
+ 5. [CORS](#5-cors)
14
+ 6. [application.yaml](#6-applicationyaml)
15
+
16
+ ---
17
+
18
+ ## 1. Dependencies
19
+
20
+ Spring Boot 4 BOM brings Spring Security 7. Add only what you need:
21
+
22
+ ```kotlin
23
+ // build.gradle.kts
24
+ dependencies {
25
+ implementation("org.springframework.boot:spring-boot-starter-security")
26
+ implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server") // JWT
27
+ implementation("org.springframework.boot:spring-boot-starter-oauth2-client") // optional: OAuth2 login
28
+ }
29
+ ```
30
+
31
+ ---
32
+
33
+ ## 2. OAuth2 Resource Server (JWT)
34
+
35
+ Validate JWTs from an authorization server (e.g. OIDC issuer). Lambda DSL is the only supported style in Security 7.
36
+
37
+ ```java
38
+ import org.springframework.context.annotation.Bean;
39
+ import org.springframework.context.annotation.Configuration;
40
+ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
41
+ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
42
+ import org.springframework.security.oauth2.jwt.JwtDecoder;
43
+ import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
44
+ import org.springframework.security.web.SecurityFilterChain;
45
+ import org.springframework.security.config.http.SessionCreationPolicy;
46
+
47
+ @Configuration
48
+ @EnableWebSecurity
49
+ public class SecurityConfig {
50
+
51
+ @Bean
52
+ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
53
+ return http
54
+ .authorizeHttpRequests(auth -> auth
55
+ .requestMatchers("/api/public/**", "/actuator/health").permitAll()
56
+ .requestMatchers("/api/admin/**").hasRole("ADMIN")
57
+ .anyRequest().authenticated())
58
+ .oauth2ResourceServer(oauth2 -> oauth2
59
+ .jwt(jwt -> jwt.decoder(jwtDecoder())))
60
+ .sessionManagement(session -> session
61
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
62
+ .csrf(csrf -> csrf.disable()) // typical for stateless API
63
+ .build();
64
+ }
65
+
66
+ @Bean
67
+ JwtDecoder jwtDecoder() {
68
+ return NimbusJwtDecoder.withJwkSetUri("https://auth.example.com/.well-known/jwks.json").build();
69
+ }
70
+ }
71
+ ```
72
+
73
+ With **issuer-uri** (Boot auto-configures `JwtDecoder`):
74
+
75
+ ```yaml
76
+ # application.yaml
77
+ spring:
78
+ security:
79
+ oauth2:
80
+ resourceserver:
81
+ jwt:
82
+ issuer-uri: https://auth.example.com
83
+ ```
84
+
85
+ Then omit the custom `JwtDecoder` bean; Boot provides it.
86
+
87
+ ---
88
+
89
+ ## 3. SecurityFilterChain (Lambda DSL)
90
+
91
+ All security configuration uses the lambda style. Example: form login + API protected by JWT.
92
+
93
+ ```java
94
+ import org.springframework.security.config.Customizer;
95
+
96
+ @Bean
97
+ SecurityFilterChain webFilterChain(HttpSecurity http) throws Exception {
98
+ return http
99
+ .securityMatcher("/api/**")
100
+ .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
101
+ .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
102
+ .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
103
+ .build();
104
+ }
105
+ ```
106
+
107
+ ---
108
+
109
+ ## 4. Method Security (@PreAuthorize)
110
+
111
+ Enable method security and use SpEL in annotations (Jakarta namespace).
112
+
113
+ ```java
114
+ @Configuration
115
+ @EnableMethodSecurity
116
+ public class MethodSecurityConfig {}
117
+ ```
118
+
119
+ ```java
120
+ import org.springframework.security.access.prepost.PreAuthorize;
121
+
122
+ @Service
123
+ public class OrderService {
124
+
125
+ @PreAuthorize("hasRole('ADMIN') or #userId == authentication.name")
126
+ public Order getOrder(String userId, String orderId) {
127
+ return orderRepository.findById(orderId).orElseThrow();
128
+ }
129
+
130
+ @PreAuthorize("hasAuthority('SCOPE_orders:write')")
131
+ public Order create(OrderRequest request) {
132
+ return orderRepository.save(map(request));
133
+ }
134
+ }
135
+ ```
136
+
137
+ Use `@PreAuthorize` / `@PostAuthorize` for read; `@PreFilter` / `@PostFilter` for filtering collections. All use Jakarta annotations in Spring Security 7.
138
+
139
+ ---
140
+
141
+ ## 5. CORS
142
+
143
+ Configure CORS in the security pipeline or globally. Example: allow a frontend origin.
144
+
145
+ ```java
146
+ import org.springframework.security.config.Customizer;
147
+ import org.springframework.web.cors.CorsConfiguration;
148
+ import org.springframework.web.cors.CorsConfigurationSource;
149
+ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
150
+
151
+ @Bean
152
+ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
153
+ return http
154
+ .cors(cors -> cors.configurationSource(corsConfigurationSource()))
155
+ .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
156
+ .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
157
+ .build();
158
+ }
159
+
160
+ @Bean
161
+ CorsConfigurationSource corsConfigurationSource() {
162
+ CorsConfiguration config = new CorsConfiguration();
163
+ config.setAllowedOrigins(List.of("https://app.example.com"));
164
+ config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
165
+ config.setAllowedHeaders(List.of("*"));
166
+ config.setAllowCredentials(true);
167
+
168
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
169
+ source.registerCorsConfiguration("/**", config);
170
+ return source;
171
+ }
172
+ ```
173
+
174
+ Or use `WebMvcConfigurer` for non-security CORS only; for APIs with credentials, the above is typical.
175
+
176
+ ---
177
+
178
+ ## 6. application.yaml
179
+
180
+ Minimal Security 7 + OAuth2 RS settings:
181
+
182
+ ```yaml
183
+ spring:
184
+ security:
185
+ oauth2:
186
+ resourceserver:
187
+ jwt:
188
+ issuer-uri: https://auth.example.com
189
+ jwk-set-uri: https://auth.example.com/.well-known/jwks.json # if no issuer-uri
190
+
191
+ # Optional: restrict actuator by profile
192
+ management:
193
+ endpoints:
194
+ web:
195
+ exposure:
196
+ include: health,info,metrics
197
+ ```
198
+
199
+ For **development only**, you can disable security (e.g. with a `SecurityFilterChain` that permits all when a profile is active). Never disable in production.
200
+
201
+ ---
202
+
203
+ **Summary:** Use `SecurityFilterChain` with lambda DSL, OAuth2 Resource Server with JWT for APIs, `@EnableMethodSecurity` and `@PreAuthorize` for method-level rules, and configure CORS in the security pipeline when the API is consumed by a browser client.
@@ -0,0 +1,119 @@
1
+ # Troubleshooting & Migration — Spring Boot 4 / Framework 7
2
+
3
+ Quick fixes for common errors and a short checklist for migrating from Boot 3 to Boot 4.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Common errors](#1-common-errors)
10
+ 2. [Boot 3 → 4 migration checklist](#2-boot-3--4-migration-checklist)
11
+
12
+ ---
13
+
14
+ ## 1. Common errors
15
+
16
+ ### javax vs jakarta
17
+
18
+ **Symptom:** Compilation errors like `package javax.servlet does not exist`, `javax.persistence` not found, or similar for `javax.*`.
19
+
20
+ **Cause:** Spring Boot 4 and Spring Framework 7 use **Jakarta EE 11**. All `javax.*` namespaces were replaced by `jakarta.*`.
21
+
22
+ **Fix:**
23
+
24
+ - Replace imports: `javax.servlet.*` → `jakarta.servlet.*`, `javax.persistence.*` → `jakarta.persistence.*`, `javax.validation.*` → `jakarta.validation.*`, etc.
25
+ - Ensure dependencies use Jakarta. Spring Boot 4 BOM already brings Jakarta-based starters. Third-party libs must be Jakarta-compatible (e.g. Hibernate 7.x, Bean Validation 3.x, Servlet API 6.x).
26
+ - Search the project for `javax.` and replace with `jakarta.` where applicable.
27
+
28
+ ---
29
+
30
+ ### RestTemplate not found or deprecated
31
+
32
+ **Symptom:** `RestTemplate` cannot be resolved, or IDE/compiler warns it is deprecated.
33
+
34
+ **Cause:** In Spring Framework 6+, `RestTemplate` is in maintenance mode. Spring 7 promotes `RestClient` for synchronous HTTP.
35
+
36
+ **Fix:**
37
+
38
+ - Add dependency (usually already present with `spring-boot-starter-web`): `RestClient` is in `spring-web`.
39
+ - Replace usage:
40
+
41
+ ```java
42
+ // Before
43
+ RestTemplate rest = new RestTemplate();
44
+ MyDto dto = rest.getForObject(url, MyDto.class);
45
+
46
+ // After (Spring 7)
47
+ RestClient rest = RestClient.create();
48
+ MyDto dto = rest.get().uri(url).retrieve().body(MyDto.class);
49
+ ```
50
+
51
+ For declarative clients, use `HttpServiceProxyFactory` + interface. See `references/spring-framework-7.md`.
52
+
53
+ ---
54
+
55
+ ### Null-safety: JSR-305 vs JSpecify
56
+
57
+ **Symptom:** Warnings or errors about `@Nullable` / `@NonNull` (e.g. wrong package or conflicting annotations).
58
+
59
+ **Cause:** Spring 7 aligns with **JSpecify** (`org.jspecify.annotations`). Legacy JSR-305 (`javax.annotation` or `org.checkerframework`) is not the standard for Spring 7.
60
+
61
+ **Fix:**
62
+
63
+ - Remove JSR-305 / Checker Framework null annotations from dependencies if possible.
64
+ - Add JSpecify and use it consistently:
65
+
66
+ ```kotlin
67
+ implementation("org.jspecify:jspecify:1.0.0")
68
+ ```
69
+
70
+ ```java
71
+ import org.jspecify.annotations.Nullable;
72
+ import org.jspecify.annotations.NonNull;
73
+ import org.jspecify.annotations.NullMarked;
74
+ ```
75
+
76
+ - Annotate packages or types with `@NullMarked` where you want strict null checking. Replace `javax.annotation.Nullable` with `org.jspecify.annotations.Nullable`, etc.
77
+
78
+ ---
79
+
80
+ ### Native image: reflection or classpath errors
81
+
82
+ **Symptom:** GraalVM native build fails with "Class not found", "Reflection without registration", or similar.
83
+
84
+ **Cause:** Native images need explicit metadata for reflection, resources, and dynamic proxies. Spring Boot AOT helps but not all code paths are covered.
85
+
86
+ **Fix:**
87
+
88
+ - Prefer **functional bean registration** instead of classpath scanning where possible. Use `ApplicationContextInitializer` or `BeanDefinitionRegistryPostProcessor` to register beans programmatically.
89
+ - For DTOs or types used in JSON/serialization, register reflection:
90
+
91
+ ```java
92
+ @RegisterReflectionForBinding({MyDto.class, OtherDto.class})
93
+ @Configuration(proxyBeanMethods = false)
94
+ public class NativeHintsConfig {}
95
+ ```
96
+
97
+ - Add GraalVM reachability metadata for third-party libraries if needed (e.g. `native-image.properties` or library-specific hints). Check [GraalVM Native Image documentation](https://www.graalvm.org/latest/reference-manual/native-image/) and Spring Boot’s native support.
98
+ - Ensure you are not loading classes only by name (e.g. `Class.forName`) without registering them for reflection.
99
+
100
+ ---
101
+
102
+ ## 2. Boot 3 → 4 migration checklist
103
+
104
+ Use this as a short guide. For authoritative steps, refer to the official [Spring Boot 4 release notes](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Release-Notes) and upgrade guides.
105
+
106
+ | Step | Action |
107
+ |------|--------|
108
+ | **Java** | Use Java 17 minimum; **Java 21 or 25** recommended. |
109
+ | **BOM** | Update parent or BOM to Spring Boot **4.0.x** (e.g. `4.0.3`). |
110
+ | **javax → jakarta** | Replace all `javax.*` imports and dependencies with `jakarta.*` (servlet, persistence, validation, etc.). |
111
+ | **Dependencies** | Align with Boot 4: Jackson **3.x**, JPA/Hibernate **3.2 / 7.x**, Bean Validation **3.1**, Spring Framework **7.0.x**. Remove or upgrade any lib that still depends on `javax.*` or old versions. |
112
+ | **RestTemplate** | Migrate to **RestClient** (or WebClient for reactive). See `references/spring-framework-7.md`. |
113
+ | **Null annotations** | Migrate to **JSpecify** (`org.jspecify`) if you use null-safety annotations. |
114
+ | **Security** | Spring Security 7 uses **lambda DSL** only. Update `SecurityFilterChain` and related config to lambda style. See `references/spring-security-7.md`. |
115
+ | **Optional: Virtual threads** | Enable with `spring.threads.virtual.enabled: true` in `application.yaml` for eligible workloads. |
116
+ | **Optional: Java 25 features** | For Structured Concurrency or Scoped Values, enable `--enable-preview` in compile and run; use Boot 4 + Java 25. See `references/spring-boot-4.md`. |
117
+ | **Tests** | Run full test suite; fix any failures due to API changes (e.g. `RestTestClient` instead of `TestRestTemplate` where applicable). Use **Testcontainers** for integration tests if needed. See `references/spring-boot-4.md` Testing section. |
118
+
119
+ After migration, verify actuator endpoints, security rules, and database access. For deeper issues, load the appropriate reference file (e.g. `spring-security-7.md`, `spring-framework-7.md`, `spring-boot-4.md`) as indicated in the skill’s Reference Files table.
@@ -9,6 +9,7 @@ plugins {
9
9
 
10
10
  group = "{{packageName}}"
11
11
  version = "0.0.1-SNAPSHOT"
12
+ description = "{{description}}"
12
13
 
13
14
  java {
14
15
  toolchain {
@@ -5,9 +5,10 @@ import org.springframework.boot.SpringApplication;
5
5
  import org.springframework.boot.autoconfigure.SpringBootApplication;
6
6
 
7
7
  /**
8
- * {{appName}} — Spring Boot {{bootVersion}} / Java {{javaVersion}} / Jakarta EE 11
8
+ * {{appName}} — {{description}}
9
+ * Spring Boot {{bootVersion}} / Java {{javaVersion}} / Jakarta EE 11
9
10
  *
10
- * Generated by create-spring-boot4 ({{year}})
11
+ * Generated by spring-boot4-skill ({{year}})
11
12
  */
12
13
  @NullMarked
13
14
  @SpringBootApplication(proxyBeanMethods = false)