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 +50 -0
- package/README.md +59 -10
- package/jest.config.cjs +9 -0
- package/lib/generator.js +14 -7
- package/lib/generator.test.js +82 -0
- package/lib/prompts.js +11 -0
- package/lib/templates.test.js +80 -0
- package/package.json +6 -2
- package/scripts/smoke-cli.sh +38 -0
- package/skills/java-spring-framework/SKILL.md +31 -0
- package/skills/java-spring-framework/references/build-templates.md +2 -0
- package/skills/java-spring-framework/references/spring-boot-4.md +108 -7
- package/skills/java-spring-framework/references/spring-framework-7.md +2 -0
- package/skills/java-spring-framework/references/spring-messaging.md +132 -0
- package/skills/java-spring-framework/references/spring-modulith.md +13 -0
- package/skills/java-spring-framework/references/spring-security-7.md +203 -0
- package/skills/java-spring-framework/references/troubleshooting-migration.md +119 -0
- package/templates/gradle-kotlin/build.gradle.kts.template +1 -0
- package/templates/gradle-kotlin/src/main/java/com/example/app/Application.java.template +3 -2
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
|
+
[](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
|
-
|
|
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
|
|
97
|
-
| `skills/java-spring-framework/references/spring-
|
|
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.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
- [
|
|
137
|
-
|
|
138
|
-
-
|
|
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
|
|
package/jest.config.cjs
ADDED
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(
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
@@ -5,9 +5,10 @@ import org.springframework.boot.SpringApplication;
|
|
|
5
5
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* {{appName}} —
|
|
8
|
+
* {{appName}} — {{description}}
|
|
9
|
+
* Spring Boot {{bootVersion}} / Java {{javaVersion}} / Jakarta EE 11
|
|
9
10
|
*
|
|
10
|
-
* Generated by
|
|
11
|
+
* Generated by spring-boot4-skill ({{year}})
|
|
11
12
|
*/
|
|
12
13
|
@NullMarked
|
|
13
14
|
@SpringBootApplication(proxyBeanMethods = false)
|