Create a Spring Boot Java project Scaffold a Spring Boot application with start.spring.io, then apply a small set of opinionated defaults. Out of scope for now: front-ends (Vue/React/Angular), cloud deployment (Azure), Git setup, CRaC and AOT images. Step 0: Detect before you create - If the working directory already contains a or , this is an existing project : do not regenerate it. Read its Spring Boot, Java and test-framework versions and follow them. Never upgrade versions implicitly ; propose an upgrade and wait for the user's decision. - Check the tools: (JDK matching the chosen Java ve…

| head -1)\" app.jar && \\\n java -Djarmode=tools -jar app.jar extract --layers --launcher --destination extracted\n\n# Module list suits typical web + JPA apps; run `jdeps --print-module-deps` if the app fails with NoClassDefFoundError.\nRUN \"$JAVA_HOME/bin/jlink\" \\\n --add-modules java.base,java.logging,java.naming,java.management,java.management.rmi,java.instrument,java.sql,java.sql.rowset,java.transaction.xa,java.xml,java.xml.crypto,java.desktop,java.security.jgss,java.security.sasl,java.scripting,java.compiler,java.net.http,java.rmi,java.prefs,jdk.unsupported,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.jfr,jdk.management,jdk.management.agent,jdk.attach,jdk.zipfs,jdk.charsets,jdk.net,jdk.security.auth \\\n --strip-debug --no-man-pages --no-header-files --compress=zip-6 \\\n --output /javaruntime\n\nFROM gcr.io/distroless/base-debian12:nonroot\n\nENV JAVA_HOME=/opt/java\nENV PATH=\"$JAVA_HOME/bin:$PATH\"\nCOPY --from=build /javaruntime $JAVA_HOME\n\nWORKDIR /app\n\nCOPY --from=build --chown=65532:65532 /app/extracted/dependencies/ ./\nCOPY --from=build --chown=65532:65532 /app/extracted/spring-boot-loader/ ./\nCOPY --from=build --chown=65532:65532 /app/extracted/snapshot-dependencies/ ./\nCOPY --from=build --chown=65532:65532 /app/extracted/application/ ./\n\nUSER nonroot\n\nENV JAVA_TOOL_OPTIONS=\"-XX:MaxRAMPercentage=75.0 -XX:+ExitOnOutOfMemoryError\"\n\nEXPOSE 8080\n\nENTRYPOINT [\"/opt/java/bin/java\", \"org.springframework.boot.loader.launch.JarLauncher\"]\n","content_type":"text/plain; charset=utf-8","language":null,"size":2514,"content_sha256":"f7bad5c2f8ee05010ffc375d6aadebc435a7c4fddb31a4df2680d5cde679212c"},{"filename":"assets/Dockerfile-native","content":"# syntax=docker/dockerfile:1\n#\n# Multi-stage GraalVM native image for a Spring Boot Maven project.\n# Adapted from jdubois/dr-jskill (Apache-2.0); see references/PROVENANCE.md.\n# Requires the `native` dependency (native-maven-plugin and the `native` profile).\n#\n# docker build -f Dockerfile-native -t my-spring-app-native .\n# docker run --rm -p 8080:8080 my-spring-app-native\n\nFROM ghcr.io/graalvm/graalvm-community:25 AS build\n\nWORKDIR /app\n\nCOPY mvnw .\nCOPY .mvn .mvn\nCOPY pom.xml .\nRUN chmod +x mvnw && ./mvnw dependency:go-offline -B\n\nCOPY src ./src\n\nENV NATIVE_IMAGE_OPTIONS=\"-H:+StaticExecutableWithDynamicLibC\"\n\nRUN ./mvnw -Pnative -DskipTests -B package native:compile\n\n# The executable is named after the artifactId; copy it to a fixed name.\n# GraalVM 25 also drops executable libjava.so/libjvm.so stubs next to it: skip them.\nRUN native_exe=$(find target -maxdepth 1 -type f -executable ! -name '*.jar' ! -name '*.original' ! -name '*.so' | head -1) && \\\n test -n \"$native_exe\" && cp \"$native_exe\" native-app\n\nFROM gcr.io/distroless/base-debian12:nonroot\n\nWORKDIR /app\n\nCOPY --from=build --chown=nonroot:nonroot /app/native-app native-app\n\nUSER nonroot\n\nEXPOSE 8080\n\nENTRYPOINT [\"./native-app\"]\n","content_type":"text/plain; charset=utf-8","language":null,"size":1211,"content_sha256":"0ebad80429948508cd1bcd80d7632e107366587fdd1f79ebfa1496f96592e9cf"},{"filename":"assets/Dockerfile-native-gradle","content":"# syntax=docker/dockerfile:1\n#\n# Multi-stage GraalVM native image for a Spring Boot Gradle project (Groovy or Kotlin DSL).\n# Adapted from jdubois/dr-jskill (Apache-2.0); see references/PROVENANCE.md.\n# Requires the `native` dependency (org.graalvm.buildtools.native plugin).\n#\n# docker build -f Dockerfile-native-gradle -t my-spring-app-native .\n# docker run --rm -p 8080:8080 my-spring-app-native\n\nFROM ghcr.io/graalvm/graalvm-community:25 AS build\n\nWORKDIR /app\n\nCOPY gradlew .\nCOPY gradle gradle\nCOPY settings.gradle* build.gradle* ./\nRUN chmod +x gradlew && ./gradlew --no-daemon dependencies > /dev/null\n\nCOPY src ./src\n\nENV NATIVE_IMAGE_OPTIONS=\"-H:+StaticExecutableWithDynamicLibC\"\n\nRUN ./gradlew --no-daemon nativeCompile\n\n# The executable is named after the project; copy it to a fixed name.\n# GraalVM 25 also drops executable libjava.so/libjvm.so stubs next to it: skip them.\nRUN native_exe=$(find build/native/nativeCompile -maxdepth 1 -type f -executable ! -name '*.jar' ! -name '*.original' ! -name '*.so' | head -1) && \\\n test -n \"$native_exe\" && cp \"$native_exe\" native-app\n\nFROM gcr.io/distroless/base-debian12:nonroot\n\nWORKDIR /app\n\nCOPY --from=build --chown=nonroot:nonroot /app/native-app native-app\n\nUSER nonroot\n\nEXPOSE 8080\n\nENTRYPOINT [\"./native-app\"]\n","content_type":"text/plain; charset=utf-8","language":null,"size":1282,"content_sha256":"b5da754fa750f992fded250ddaff2591dfec5915c6e250d03d3ac1af41c9f4d2"},{"filename":"references/configuration.md","content":"# Configuration\n\n## Format\n\nUse `application.properties` or `application.yml`, **matching what the project already uses**. New projects generated by start.spring.io get `application.properties`; YAML is fine for deeply nested settings. Do not mix both in one project.\n\n## Profiles\n\n```properties\n# application.properties (shared defaults)\nspring.application.name=demo\nspring.jpa.open-in-view=false\nserver.shutdown=graceful\n```\n\n- `application-dev.properties`, `application-prod.properties`, activated with `SPRING_PROFILES_ACTIVE` (environment variable) or `--spring.profiles.active`.\n- Keep prod-safe values in the base file; override only what differs in dev (e.g. `logging.level.com.example=DEBUG`).\n- Tests: use `src/test/resources/application.properties` or `@TestPropertySource`, not a \"test\" profile checked into `main`.\n\n## Externalized values and secrets\n\n- Read values from environment variables with defaults for local development: `spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/mydb}`.\n- Spring Boot also maps `SPRING_DATASOURCE_URL` to `spring.datasource.url` automatically (relaxed binding).\n- For a local `.env` file: `spring.config.import=optional:file:.env[.properties]`. Add `.env` to `.gitignore`; commit `.env.sample` with placeholder values.\n- Never commit passwords, tokens or API keys. In production use the platform's secret store (Kubernetes secrets, Vault, cloud key vault) exposed as environment variables or config trees (`spring.config.import=configtree:/run/secrets/`).\n\n## Type-safe properties\n\n```java\n@ConfigurationProperties(prefix = \"app.mail\")\n@Validated\npublic record MailProperties(@NotBlank String from, @Positive int retries) {}\n```\n\nEnable with `@ConfigurationPropertiesScan` on the application class (or `@EnableConfigurationProperties`). Prefer this over scattered `@Value`. Spring Boot's annotation processor (`spring-boot-configuration-processor`, optional dependency) generates IDE metadata.\n\n## Checklist\n\n- [ ] One format (properties or YAML) used consistently\n- [ ] No secrets in the repository\n- [ ] Defaults work locally; production values come from the environment\n- [ ] `@ConfigurationProperties` with validation for custom settings\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":2219,"content_sha256":"7c383beed85570467af906bb652adb3cf17b7e09e00747ddec6ea83c653aa127"},{"filename":"references/database.md","content":"# Database (PostgreSQL)\n\nDefaults: PostgreSQL (image `postgres:18-alpine`, see [versions.md](versions.md)), Spring Data JPA, Hibernate, HikariCP. Dependencies: `data-jpa`, `postgresql`, `docker-compose`, `testcontainers`.\n\n## Configuration\n\n```properties\nspring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/mydb}\nspring.datasource.username=${SPRING_DATASOURCE_USERNAME:user}\nspring.datasource.password=${SPRING_DATASOURCE_PASSWORD:password}\n\nspring.jpa.hibernate.ddl-auto=update\nspring.jpa.open-in-view=false\nspring.jpa.show-sql=false\n```\n\nThe driver class is detected from the URL; do not set it. The defaults above are for local development only: production credentials come from the environment (see [configuration.md](configuration.md)).\n\n## Schema management (`ddl-auto`)\n\nFollow the schema management rule of the `java-hibernate-jpa` skill (prototype/local, production with Flyway/Liquibase, or externally managed). The table and setup below apply it to Spring Boot.\n\n| Value | Behaviour | Use for |\n|-------|-----------|---------|\n| `update` | Adds missing tables/columns, never drops | Local development / prototypes |\n| `validate` | Fails at startup if schema and entities differ | Production |\n| `create` / `create-drop` | Recreates schema at startup (and drops at shutdown) | Throw-away tests |\n| `none` | Hibernate does nothing | Schema managed elsewhere |\n\nHibernate's `update` is convenient but cannot rename, drop or migrate data. For production schema evolution, **Flyway or Liquibase are optional but recommended**: add the starter: on Boot 4 `spring-boot-starter-flyway` plus `org.flywaydb:flyway-database-postgresql` (or `spring-boot-starter-liquibase`), because Boot 4 auto-configuration lives in the starter; on Boot 3 add `org.flywaydb:flyway-core` plus `flyway-database-postgresql` (or `liquibase-core`) directly. Put migrations in `src/main/resources/db/migration` (`V1__init.sql`), and set `ddl-auto=validate`. Add them when the user wants versioned migrations or the app is headed to production.\n\n## Entities\n\nKeep entities annotated precisely (`@Table`, `@Column(length=..., nullable=...)`, `@Index`) so generated or validated DDL is accurate. Use `GenerationType.IDENTITY` or a sequence, `Instant` for timestamps, and avoid exposing entities in the API (use DTOs/records).\n\n## Local development: Docker Compose\n\nWith `spring-boot-docker-compose`, Spring Boot starts `compose.yaml` automatically and wires the datasource (no URL needed):\n\n```yaml\nservices:\n postgres:\n image: postgres:18-alpine\n environment:\n POSTGRES_DB: mydb\n POSTGRES_USER: user\n POSTGRES_PASSWORD: password\n ports:\n - \"5432\"\n healthcheck:\n test: [\"CMD\", \"pg_isready\", \"-U\", \"user\", \"-d\", \"mydb\"]\n interval: 10s\n timeout: 5s\n retries: 5\n```\n\n## Tests\n\nUse Testcontainers with `@ServiceConnection`; see the `spring-boot-testing` skill (`references/testcontainers-jdbc.md`). Pin the image tag; never `latest`. start.spring.io generates `postgres:latest` in `compose.yaml` and `TestcontainersConfiguration`: replace both with `postgres:18-alpine` (verified).\n\n## Performance and production\n\n- HikariCP is the default pool; tune `spring.datasource.hikari.maximum-pool-size` and `connection-timeout`.\n- Avoid N+1 queries: `JOIN FETCH` or `@EntityGraph`; use DTO projections for read models.\n- Keep `open-in-view=false` and `@Transactional` on services.\n- Index foreign keys and filtered/sorted columns; check plans with `EXPLAIN ANALYZE`.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":3511,"content_sha256":"0e7c5ee45cffcad6033e0cad27bcb950a5604d557ee2c2c261479ceed65d859a"},{"filename":"references/docker.md","content":"# Docker\n\nFour multi-stage Dockerfiles are provided: JVM and GraalVM native images, each for Maven and Gradle (Groovy or Kotlin DSL). Copy the one you need to the project root and build it with `-f`. AOT and CRaC images are deferred.\n\n| | Maven | Gradle |\n|--|-------|--------|\n| JVM image | [../assets/Dockerfile](../assets/Dockerfile) | [../assets/Dockerfile-gradle](../assets/Dockerfile-gradle) |\n| Native image | [../assets/Dockerfile-native](../assets/Dockerfile-native) | [../assets/Dockerfile-native-gradle](../assets/Dockerfile-native-gradle) |\n\nVerified on Boot 4.1.1 (web + actuator + validation), arm64 Docker Desktop: all four build and serve `/hello` and `/actuator/health`. JVM images are about 200 MB and build in under a minute (about 2 s startup); native images are about 175 MB, take 2m20-2m55 to build (around 6 GB of builder memory) and start in about 0.09 s. The Gradle builds set the toolchain to the same Java version as the build stage (25).\n\n## Choosing\n\n| | JVM image | Native image |\n|--|-----------|--------------|\n| Build time | Fast | Slow, memory hungry (several GB) |\n| Startup / memory | Seconds / higher | Milliseconds / low |\n| Compatibility | Everything | Needs GraalVM-compatible libraries; reflection hints may be needed |\n| Pick when | Default choice | Scale-to-zero, CLI-like, tight memory limits |\n\nNative requires the `native` dependency at generation time (`-d dependencies=...,native`) and the Spring Boot `native` Maven profile (included by start.spring.io when the dependency is selected).\n\n## Properties of the provided Dockerfiles\n\n- Multi-stage: build in a JDK/GraalVM image, run in a small runtime image.\n- JVM: the Boot jar is extracted into layers (`java -Djarmode=tools -jar app.jar extract --layers --launcher`) for better caching, runtime assembled with `jlink`, final base is distroless `nonroot` (no shell, no package manager).\n- Native: the executable is built with `-H:+StaticExecutableWithDynamicLibC` and copied into distroless `nonroot`. The `find` that picks the executable must skip `*.so`: GraalVM 25 leaves executable `libjava.so`/`libjvm.so` stubs next to it, and copying one of those gives a container that exits with 139 (segfault).\n- Run as non-root, expose 8080, JVM flags via `JAVA_TOOL_OPTIONS` (no shell to expand `JAVA_OPTS`).\n- Pinned base image tags (`eclipse-temurin:25-jdk-noble`, `ghcr.io/graalvm/graalvm-community:25`, `gcr.io/distroless/base-debian12:nonroot`); match the Java version of the project and update when it changes. Consider pinning by digest for reproducibility.\n- No Docker `HEALTHCHECK` (distroless has no curl); probe `/actuator/health` from the orchestrator (liveness/readiness: `management.endpoint.health.probes.enabled=true`).\n- Keep a `.dockerignore` (`target/`, `build/`, `.git`, `.env`, IDE files) so secrets and build output stay out of the context.\n\n## Build and run\n\n```bash\ndocker build -t my-spring-app .\ndocker run --rm -p 8080:8080 \\\n -e SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/mydb \\\n -e SPRING_DATASOURCE_USERNAME=user -e SPRING_DATASOURCE_PASSWORD=password \\\n my-spring-app\n```\n\nAlternative without a Dockerfile: `./mvnw spring-boot:build-image` or `./gradlew bootBuildImage` (Cloud Native Buildpacks; add `-Pnative` for native with Maven, Gradle needs the `org.graalvm.buildtools.native` plugin). Verified on Boot 4.1.1 (web + actuator + validation + data-jpa, Java 25): Maven native build-image about 2m40, 282 MB image; Gradle `bootBuildImage` 2-4 min, image starts in 0.1-0.2 s and `/actuator/health` returns UP with native-build-tools 1.1.7 and 1.1.8. A native image that crashes at startup with `Invalid logger interface org.hibernate.validator.internal.util.logging.Log (implementation not found)` was seen once with Gradle but could not be reproduced; it matches [graalvm/native-build-tools#1017](https://github.com/graalvm/native-build-tools/issues/1017) (Gradle `collectReachabilityMetadata` overwrote the hibernate-validator metadata with the jboss-logging one, leaving a ~2 KB file). If it happens, check `unzip -l build/libs/*.jar | grep hibernate-validator.*reachability-metadata.json` (healthy is about 150 KB), upgrade `org.graalvm.buildtools.native` to the latest release and rebuild with `--no-build-cache`; always run the image and hit `/actuator/health`.\n\n## Development\n\nUse `spring-boot-docker-compose` with `compose.yaml` for dependencies (see [database.md](database.md)); do not containerize the application itself during development.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":4503,"content_sha256":"1f38198df919253b2b0fc1769ddd9b9e99738ea6928dd47cfacf89ab5cbf09d1"},{"filename":"references/logging.md","content":"# Logging\n\nSpring Boot logs with Logback through SLF4J by default. Code must depend only on the SLF4J API (`org.slf4j.Logger`), never on Logback or `System.out`.\n\n```java\nprivate static final Logger logger = LoggerFactory.getLogger(OrderService.class);\n\nlogger.info(\"Order {} created for customer {}\", order.id(), customerId);\nlogger.error(\"Payment failed for order {}\", orderId, exception); // exception last, no placeholder\n```\n\n## Rules\n\n- Parameterized messages, not string concatenation.\n- Levels: `ERROR` = needs attention; `WARN` = recoverable anomaly; `INFO` = business events and lifecycle; `DEBUG`/`TRACE` = diagnostics (off in production).\n- Never log secrets, tokens, passwords or personal data; log identifiers instead.\n- Log an exception once, where it is handled, with the stack trace.\n- Do not log inside tight loops.\n\n## Configuration\n\n```properties\nlogging.level.root=INFO\nlogging.level.com.example=INFO\n# dev profile: logging.level.com.example=DEBUG\n```\n\nPrefer properties over a custom `logback-spring.xml`; add the XML only for needs properties cannot cover (appenders, rolling files).\n\n## Structured logging (Spring Boot 3.4+)\n\nFor production and log aggregation, emit JSON without extra dependencies:\n\n```properties\nlogging.structured.format.console=ecs\n```\n\nOther built-in formats: `logstash`, `gelf`. In containers, log to the console and let the platform collect it.\n\n## Correlation\n\nUse Micrometer Tracing / the `spring-observability` skill so trace and span ids appear in logs; use MDC for request-scoped values (`MDC.put(\"requestId\", ...)`, always cleared in `finally`).\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":1600,"content_sha256":"e06287625c79e2aafec727e27b2882a3c126d7baf02c595e747ed1402fa4476a"},{"filename":"references/PROVENANCE.md","content":"# Provenance\n\nThis skill and its references are adapted from **dr-jskill** by Julien Dubois (the contributor of this skill).\n\n- Source: https://github.com/jdubois/dr-jskill\n- Commit read: `29e71996048e4086bfa389268a3c6c2e49ea3f25`\n- License of the source: Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0)\n- The adapted material is contributed to this repository by its original author (Julien Dubois, sole copyright holder of dr-jskill) under this repository's MIT license, and remains available upstream under Apache-2.0. Keep the attribution and this notice with any redistribution of the adapted files.\n\n## Files adapted\n\n| Source file in dr-jskill | Adapted into |\n|--------------------------|--------------|\n| `SKILL.md` | `SKILL.md` (rewritten, shortened, Maven and Gradle) |\n| `versions.json`, `scripts/lib/versions.mjs` | `references/versions.md` (documented procedure and fallback values; script not copied) |\n| `references/CONFIGURATION.md` | `references/configuration.md` (condensed) |\n| `references/DATABASE.md` | `references/database.md` (condensed; Flyway/Liquibase noted as optional) |\n| `references/LOGGING.md` | `references/logging.md` (condensed) |\n| `references/SECURITY.md` | `references/security.md` (condensed to basics) |\n| `references/DOCKER.md`, `assets/Dockerfile`, `assets/Dockerfile-native` | `references/docker.md`, `assets/Dockerfile`, `assets/Dockerfile-native` (front-end build steps removed) |\n| `references/SPRING-BOOT-4.md` | `references/spring-boot-4.md` (condensed) |\n\n## Changes\n\nChanges were made: content was condensed, front-end, Azure, Git, CRaC and AOT material was removed (deferred), Gradle support was added, and scripts were replaced by documented `curl` procedures against start.spring.io.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":1754,"content_sha256":"b8a6623408b4d166ee9d8c0ec954c4d4434808d6f700295d07249bdbba236b4c"},{"filename":"references/security.md","content":"# Security basics\n\nFor anything beyond the basics (OAuth2/OIDC, JWT resource server, method security, CSRF/CORS details, testing security) use the `spring-security` skill. This page covers secure defaults when scaffolding.\n\n## Dependencies\n\nAdd `security` at generation time (Boot 4: `spring-boot-starter-security`, plus `spring-boot-starter-security-test` in test scope if generated by start.spring.io). Adding it secures every endpoint by default.\n\n## Minimal explicit configuration\n\n```java\n@Configuration\n@EnableWebSecurity\nclass SecurityConfig {\n\n @Bean\n SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n http\n .authorizeHttpRequests(auth -> auth\n .requestMatchers(\"/actuator/health/**\", \"/actuator/info\").permitAll()\n .anyRequest().authenticated())\n .httpBasic(Customizer.withDefaults());\n return http.build();\n }\n\n}\n```\n\n- For local development, leave Boot's generated user and password encoder setup intact. Do not add a `PasswordEncoder` bean alone: Boot's generated password is then left unencoded and cannot authenticate with a delegating encoder.\n- Deny by default: `anyRequest().authenticated()` last.\n- Keep CSRF enabled for browser sessions; disable it only for stateless token APIs, deliberately.\n- For production password authentication, configure a persistent user store and a matching `PasswordEncoder` together. Use `PasswordEncoderFactories.createDelegatingPasswordEncoder()` and store its encoded `{id}hash` values, never plain text.\n- Do not use `{noop}` passwords or `InMemoryUserDetailsManager` demo users outside local experiments.\n\n## Secure defaults checklist\n\n- [ ] Secrets from environment/secret store, never committed (see [configuration.md](configuration.md))\n- [ ] Expose only `health` and `info` over HTTP: `management.endpoints.web.exposure.include=health,info`\n- [ ] Validate input (`@Valid`, Bean Validation) and use parameterized queries / Spring Data\n- [ ] Error responses via RFC 9457 Problem Details: `spring.mvc.problemdetails.enabled=true`; no stack traces to clients (`server.error.include-stacktrace=never`)\n- [ ] CORS configured explicitly with allowed origins, never `*` with credentials\n- [ ] HTTPS terminated at the platform; `server.forward-headers-strategy=framework` behind a proxy\n- [ ] Dependencies kept current; container runs as non-root (see [docker.md](docker.md))\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":2420,"content_sha256":"1ac9bdaefd01eba4daccf60554dc01742498e6030fc9e18100bf3eb89a1d6dc2"},{"filename":"references/spring-boot-4.md","content":"# Spring Boot 3 vs 4 (when generating or editing code)\n\nDetect the project's Spring Boot major first (see [versions.md](versions.md)); apply only the section that matches. Verify against the [Spring Boot 4.0 migration guide](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide).\n\n## Baselines\n\n| | Boot 3.x | Boot 4.x |\n|--|----------|----------|\n| Spring Framework | 6.x | 7.x |\n| Java | 17+ | 17+ (25 recommended) |\n| Jakarta EE | 10 | 11 |\n| Jackson | 2 (`com.fasterxml.jackson.databind`) | 3 (`tools.jackson.databind`) |\n| Testcontainers | 1.x | 2.x |\n\n## Things that commonly break in Boot 4\n\n1. **Modular starters.** Web is `spring-boot-starter-webmvc` (not `-web`), and each technology has a matching test starter (`spring-boot-starter-webmvc-test`, `spring-boot-starter-data-jpa-test`...). Generate with start.spring.io rather than guessing artifact names.\n2. **Jackson 3.** Only API classes move (`tools.jackson.databind.ObjectMapper`); annotations stay in `com.fasterxml.jackson.annotation`.\n3. **Test annotations moved**, for example `@WebMvcTest` is now `org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest`. `@MockitoBean` / `@MockitoSpyBean` replace `@MockBean` / `@SpyBean` (deprecated since 3.4 and removed in Boot 4; use the new ones everywhere Boot 3.4+ allows). See the `spring-boot-testing` skill.\n4. **Testcontainers 2.x.** Artifacts are prefixed (`testcontainers-postgresql`, `testcontainers-junit-jupiter`) and classes moved (`org.testcontainers.postgresql.PostgreSQLContainer`). With Boot 3 keep the 1.x names.\n5. **Main class for AOT/native.** If `process-aot` cannot find the main class, set `\u003cstart-class>` (Maven property) to the fully qualified `@SpringBootApplication` class.\n6. **Make the generated `TestcontainersConfiguration` public** if tests live in sub-packages.\n\n## Boot 3 projects\n\nStay on Boot 3 when the project is on Boot 3 (start.spring.io cannot generate new 3.x projects, see [versions.md](versions.md)): use `spring-boot-starter-web`, `org.testcontainers.containers.PostgreSQLContainer` (Testcontainers 1.x), `@MockBean`, Jackson 2. Do not migrate unless asked; Boot 3.5 → 4.0 is a separate, deliberate upgrade.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":2202,"content_sha256":"e455b4c568c207fc68dbfff8c1af6db8014a6167f538818ec420f79701d335b1"},{"filename":"references/versions.md","content":"# Version handling\n\n## Rules\n\n1. **Existing project:** read versions from `pom.xml` / `build.gradle(.kts)` (Spring Boot parent or plugin version, `java.version`/toolchain, `spring-boot-starter-test`). Keep them. Never bump implicitly.\n2. **New project:** resolve at run time, fall back to the table below only when offline.\n3. Never use `latest`, `LATEST` or `RELEASE` as a version, nor piped shell installers.\n\n## Resolve the latest stable Spring Boot\n\n```bash\ncurl -fsS -H 'Accept: application/json' https://start.spring.io/metadata/client \\\n | python3 -c \"import json,sys; d=json.load(sys.stdin); print(d['bootVersion']['default'], d['javaVersion']['default'])\"\n```\n\n- `bootVersion.default` is the current stable release (it may carry a `.RELEASE` suffix such as `4.1.1.RELEASE`; strip it: pass `4.1.1`).\n- Skip ids containing `SNAPSHOT`, `M` or `RC` (milestones/snapshots) unless the user asks for them.\n- `javaVersion.default` is only the start.spring.io default. Pick the highest LTS the installed JDK supports (`java -version`), and make sure it is offered in `javaVersion.values`.\n- Use the major line the user asks for. If they do not say, use the latest stable; see [spring-boot-4.md](spring-boot-4.md) for what changes between 3.x and 4.x.\n- start.spring.io only generates Boot 4.0+ (verified: `bootVersion=3.5.x` returns HTTP 400 \"compatibility range is >=4.0.0\"). For a Boot 3.5 project, generate nothing: hand-write the build file with `spring-boot-starter-parent` 3.5.x (latest 3.5 on Maven Central, e.g. 3.5.16), `spring-boot-starter-web`, and `spring-boot-starter-test`, plus the Maven/Gradle wrapper. Creating the wrapper (`mvn wrapper:wrapper` / `gradle wrapper`) requires Maven or Gradle to be installed locally; if neither is available, ask the user to install one or choose Boot 4.\n- Verify the choice exists on Maven Central: `curl -fsSI https://repo1.maven.org/maven2/org/springframework/boot/spring-boot/BOOT_VERSION/spring-boot-BOOT_VERSION.pom`.\n\n## Fallback values (offline, reviewed 2026-10)\n\n| Item | Value |\n|------|-------|\n| Spring Boot | 4.1.1 |\n| Java | 25 (LTS) |\n| PostgreSQL image | `postgres:18-alpine` |\n| Testcontainers | managed by the Spring Boot BOM; do not pin unless overriding |\n| JDK image | `eclipse-temurin:25-jdk-noble` |\n| GraalVM image | `ghcr.io/graalvm/graalvm-community:25` |\n\nThese values age: when start.spring.io is reachable, prefer its answer, and tell the user which source was used.\n\n## Dependencies managed by the BOM\n\nDo not specify versions for Spring-managed dependencies (Hibernate, Jackson, Testcontainers, PostgreSQL driver, JUnit). Pin only what the BOM does not manage (for example a Maven plugin or a Docker image tag).\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":2694,"content_sha256":"feb2a00db404425ab6d05cb68be6bdef56b74886309b699fbc187fa5fcb270ec"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Create a Spring Boot Java project","type":"text"}]},{"type":"paragraph","content":[{"text":"Scaffold a Spring Boot application with ","type":"text"},{"text":"start.spring.io","type":"text","marks":[{"type":"link","attrs":{"href":"https://start.spring.io","title":null}}]},{"text":", then apply a small set of opinionated defaults. Out of scope for now: front-ends (Vue/React/Angular), cloud deployment (Azure), Git setup, CRaC and AOT images.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step 0: Detect before you create","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If the working directory already contains a ","type":"text"},{"text":"pom.xml","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"build.gradle(.kts)","type":"text","marks":[{"type":"code_inline"}]},{"text":", this is an ","type":"text"},{"text":"existing project","type":"text","marks":[{"type":"strong"}]},{"text":": do not regenerate it. Read its Spring Boot, Java and test-framework versions and follow them. ","type":"text"},{"text":"Never upgrade versions implicitly","type":"text","marks":[{"type":"strong"}]},{"text":"; propose an upgrade and wait for the user's decision.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check the tools: ","type":"text"},{"text":"java -version","type":"text","marks":[{"type":"code_inline"}]},{"text":" (JDK matching the chosen Java version), ","type":"text"},{"text":"docker --version","type":"text","marks":[{"type":"code_inline"}]},{"text":" (needed for Testcontainers and Compose). On the default path (current Boot via start.spring.io) Maven/Gradle need not be installed: the generated wrapper is used. The Boot 3.5 fallback needs Maven or Gradle installed once to create the wrapper (see ","type":"text"},{"text":"versions.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/versions.md","title":null}}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ask only for what is missing: project name, group id, package, build tool (default ","type":"text"},{"text":"Maven","type":"text","marks":[{"type":"strong"}]},{"text":"), dependencies (default: web, actuator, validation, devtools; add ","type":"text"},{"text":"data-jpa","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"postgresql","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"security","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"docker-compose","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"testcontainers","type":"text","marks":[{"type":"code_inline"}]},{"text":" when a database or auth is needed).","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step 1: Resolve versions","type":"text"}]},{"type":"paragraph","content":[{"text":"Follow ","type":"text"},{"text":"references/versions.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/versions.md","title":null}}]},{"text":": query ","type":"text"},{"text":"https://start.spring.io/metadata/client","type":"text","marks":[{"type":"code_inline"}]},{"text":" at run time for the latest stable Spring Boot release, fall back to the documented pinned values if offline. Do not hardcode ","type":"text"},{"text":"latest","type":"text","marks":[{"type":"code_inline"}]},{"text":" anywhere.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step 2: Generate the project","type":"text"}]},{"type":"paragraph","content":[{"text":"Use the start.spring.io HTTP API with explicit, pinned parameters (no piped installers). Replace the values in capitals.","type":"text"}]},{"type":"paragraph","content":[{"text":"Maven:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"curl -fsS https://start.spring.io/starter.zip \\\n -d type=maven-project -d language=java -d packaging=jar \\\n -d bootVersion=BOOT_VERSION -d javaVersion=JAVA_VERSION \\\n -d groupId=com.example -d artifactId=demo -d name=demo \\\n -d packageName=com.example.demo -d baseDir=demo \\\n -d dependencies=web,actuator,validation,devtools \\\n -o demo.zip && unzip -q demo.zip && rm demo.zip","type":"text"}]},{"type":"paragraph","content":[{"text":"Gradle: same command with ","type":"text"},{"text":"-d type=gradle-project","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Groovy DSL) or ","type":"text"},{"text":"-d type=gradle-project-kotlin","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Kotlin DSL; the language stays ","type":"text"},{"text":"java","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]},{"type":"paragraph","content":[{"text":"Inspect the result (","type":"text"},{"text":"ls demo","type":"text","marks":[{"type":"code_inline"}]},{"text":", read the build file) and make sure the wrapper (","type":"text"},{"text":"mvnw","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"gradlew","type":"text","marks":[{"type":"code_inline"}]},{"text":") is executable. Add ","type":"text"},{"text":"-d dependencies=...,data-jpa,postgresql,docker-compose,testcontainers","type":"text","marks":[{"type":"code_inline"}]},{"text":" for a database project.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step 3: Apply the defaults","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Area","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"What to do","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Reference","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Boot 3 vs 4 differences","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Starter names, Jackson 3, test modules","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/spring-boot-4.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/spring-boot-4.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Configuration","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"application.properties","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":".yml","type":"text","marks":[{"type":"code_inline"}]},{"text":" (match the project), profiles, ","type":"text"},{"text":"@ConfigurationProperties","type":"text","marks":[{"type":"code_inline"}]},{"text":", secrets via environment","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/configuration.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/configuration.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Database","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PostgreSQL, Hibernate settings, Compose for dev, Testcontainers for tests","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/database.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/database.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Logging","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SLF4J, parameterized messages, levels, structured logging","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/logging.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/logging.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Security","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Spring Security basics, secure defaults, Problem Details","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/security.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/security.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Docker","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"JVM image and GraalVM native image Dockerfiles","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/docker.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/docker.md","title":null}}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Testing","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"See the ","type":"text"},{"text":"spring-boot-testing","type":"text","marks":[{"type":"code_inline"}]},{"text":" skill (slices, Testcontainers, Failsafe)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"skills/spring-boot-testing","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Best practices","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"See the ","type":"text"},{"text":"java-springboot","type":"text","marks":[{"type":"code_inline"}]},{"text":" skill and ","type":"text"},{"text":"instructions/springboot.instructions.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"skills/java-springboot","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"paragraph","content":[{"text":"Keep dotfiles minimal: a ","type":"text"},{"text":".gitignore","type":"text","marks":[{"type":"code_inline"}]},{"text":" covering ","type":"text"},{"text":"target/","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"build/","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":".gradle/","type":"text","marks":[{"type":"code_inline"}]},{"text":", IDE folders and ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":"; never commit ","type":"text"},{"text":".env","type":"text","marks":[{"type":"code_inline"}]},{"text":" (commit a ","type":"text"},{"text":".env.sample","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Step 4: Verify","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Build and test: ","type":"text"},{"text":"./mvnw verify","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Maven) or ","type":"text"},{"text":"./gradlew build","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Gradle).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run: ","type":"text"},{"text":"./mvnw spring-boot:run","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"./gradlew bootRun","type":"text","marks":[{"type":"code_inline"}]},{"text":", then ","type":"text"},{"text":"curl -fsS localhost:8080/actuator/health","type":"text","marks":[{"type":"code_inline"}]},{"text":" must return ","type":"text"},{"text":"{\"status\":\"UP\"}","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Report the chosen versions and anything that was skipped or deferred.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Provenance","type":"text"}]},{"type":"paragraph","content":[{"text":"Adapted from the Apache-2.0 skill ","type":"text"},{"text":"jdubois/dr-jskill","type":"text","marks":[{"type":"link","attrs":{"href":"https://github.com/jdubois/dr-jskill","title":null}}]},{"text":"; see ","type":"text"},{"text":"references/PROVENANCE.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/PROVENANCE.md","title":null}}]},{"text":".","type":"text"}]}]},"metadata":{"date":"2026-11-02","name":"create-spring-boot-java-project","author":"@skillopedia","source":{"stars":40722,"repo_name":"awesome-copilot","origin_url":"https://github.com/github/awesome-copilot/blob/HEAD/skills/create-spring-boot-java-project/SKILL.md","repo_owner":"github","body_sha256":"b4b65b93468685467e9a8e6f4c695a870c510a068a753be6d960f740c03a2b93","cluster_key":"6814c941291d87e4461b952829032a6d71e96b909353caf5c9a84198fb093b11","clean_bundle":{"format":"clean-skill-bundle-v1","source":"github/awesome-copilot/skills/create-spring-boot-java-project/SKILL.md","text_bytes":28101,"source_commit":"61fa1fa8fcc63d6dbd2f07a75aeb8e214df5acfb","skills_sh_rank":2582,"attachment_count":12,"text_attachments":12,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"skills/create-spring-boot-java-project/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"software-engineering","category_label":"Engineering"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"software-engineering","import_tag":"skills-sh-top20k-2026-11","description":"Create a new Spring Boot Java project (Maven or Gradle) from start.spring.io with sensible defaults for configuration, PostgreSQL, logging, security, testing and Docker. Use when asked to scaffold, bootstrap or generate a Spring Boot application. Detects existing project versions and never upgrades implicitly."}},"renderedAt":1793670931475}

Create a Spring Boot Java project Scaffold a Spring Boot application with start.spring.io, then apply a small set of opinionated defaults. Out of scope for now: front-ends (Vue/React/Angular), cloud deployment (Azure), Git setup, CRaC and AOT images. Step 0: Detect before you create - If the working directory already contains a or , this is an existing project : do not regenerate it. Read its Spring Boot, Java and test-framework versions and follow them. Never upgrade versions implicitly ; propose an upgrade and wait for the user's decision. - Check the tools: (JDK matching the chosen Java ve…