Setup Java Spring Boot GitHub Workflows

This documentation was generated with the assistance of AI. Please report any inaccuracies.

Creates the GitHub Actions workflows a Spring Boot service needs: one that builds, tests, analyses, and publishes documentation (optionally running a Hilla frontend’s tests too); one that deploys (as a staged rollout when rolling deployments are enabled, with a further promote.yml to advance or roll it back); one that stops or destroys an environment; and one that runs dependency/code/secret security scans, plus a Dependabot config.

Purpose

iru-setup-java-springboot-github-workflows is the service-shaped counterpart to Setup Java GitHub Workflows. A service is not published to Maven Central and is not GPG-signed — it is built into a container image and deployed — so the publish, sign, and version-sync stages are replaced by image build, OpenTofu apply, and teardown. The shared stages (test, analyse, Sonar, Antora, GitHub Pages) follow the same house pattern, so the two remain recognisably related.

The skill announces whether it is creating the workflows from scratch or updating existing ones, and asks per existing file whether to Update, Keep as is, Replace, or Stop (or applies on-existing). It also resolves visibility-aware CI choices: visibility, sonar, ci-tests (unit runs Surefire only with -DskipITs; full also runs Failsafe against Testcontainers) and pages (no uploads the built docs as a workflow artifact instead of publishing to GitHub Pages). For private or internal repositories it states the Actions-minutes cost (2,000/month on Free, 3,000 on Pro and Team, 50,000 on Enterprise Cloud; Linux 2-core $0.006/min) and that GitHub Pages needs Pro, Team or Enterprise Cloud and is public on the internet unless Enterprise Cloud access control is used.

The build.yml Sonar step is no longer offered reactively: it reads the manifest’s sonar.mode (cloud/self-hosted/none, defaulting to cloud for a manifest written before the field existed) as the source of truth and drops the step entirely — rather than disabling it with a permanently-false condition — when sonar.mode: none. When the manifest’s frontend field is hilla, build.yml also sets up Node, runs the frontend’s Vitest suite with coverage (after the Maven build, since the frontend’s generated config imports compiled helpers that only exist once Maven has reached vaadin:prepare-frontend), and packages with -Pproduction -Dvaadin.ci.build=true.

Workflow Trigger Does

build.yml

Push to the integration/main branch, and every pull request

Build, unit tests (Surefire), Testcontainers integration tests (Failsafe, only when ci.tests: full), static analysis, aggregated coverage, Sonar (when sonar.mode isn’t none), frontend Vitest tests and coverage (when frontend: hilla), and — on the main branch only — the Antora site plus the generated OpenAPI, protobuf, GraphQL and AsyncAPI HTML, published to GitHub Pages when ci.pages: yes, otherwise uploaded as a documentation workflow artifact

deploy.yml

workflow_dispatch with an environment input, optionally on release published

Build and push the container image, authenticate via keyless OIDC, tofu plan then apply that plan file, then a smoke check that fails the deploy if the service never becomes healthy

promote.yml

workflow_dispatch with environment and target inputs; only with rolling deployments, and not for strategy rolling

Advance a rollout one step, or roll it back or abort it. Stateless: it reads the current traffic split from the cloud (ECS weighted target groups, Cloud Run traffic tags, Argo Rollouts) and sets the next one. It needs no GitHub Environment reviewers, so it works on any GitHub plan

undeploy.yml

workflow_dispatch only

Scale to zero (the reversible default) or destroy an environment, behind a typed-confirmation check and a protected GitHub Environment

security.yml

Push to the main branch, every pull request, and a weekly schedule

Dependency review, CodeQL (with a javascript-typescript matrix leg when frontend: hilla), OSV-Scanner, and gitleaks — each job individually omittable via its own args flag

.github/dependabot.yml

n/a — configuration, not a workflow

Grouped weekly updates for the maven (or gradle), docker, and github-actions ecosystems, plus npm (pointed at the real web module) when the manifest’s frontend isn’t none; always generated regardless of the security-* flags

No credential is ever written into a workflow file: cloud access is keyless via OIDC, and everything else is a repository or environment secret referenced by name. Where the registry supports it, even the registry credential is derived from the OIDC session (amazon-ecr-login, gcloud auth configure-docker) rather than stored.

The guards on undeploy.yml are the feature rather than ceremony: stop is the default and is reversible, data-bearing resources are expected to carry deletion protection so destroy fails on them rather than silently deleting a production database, and the skill recommends restricting prod to required reviewers plus a deployment branch rule — and considering omitting prod from the destroy path entirely.

Language and build tool

project.language (java/kotlin, absent means java) and project.buildTool (maven/gradle, absent means maven; Gradle only ever appears with Kotlin) select one of three variants, confirmed against the disk (pom.xml, settings.gradle.kts plus gradlew, src/main/kotlin) — if the manifest and the disk disagree, the disk wins for the run and the report says so.

Variant What changes

java + maven

The base templates, unchanged.

kotlin + maven

Static analysis is detekt plus ktlint instead of Checkstyle/PMD/SpotBugs, and code documentation is Dokka instead of Javadoc; every mvn build, test, Sonar and image command is unchanged.

kotlin + gradle

Every job uses gradle/actions/setup-gradle (open-source basic cache provider) and ./gradlew, never a runner-installed gradle: test/integrationTest, :coverage:testCodeCoverageReport, detekt ktlintCheck, dokkaGeneratePublicationHtml, and the image built with bootBuildImage tagged from the gradle.properties version (<version>-<short sha>). No Maven site is published (the test and coverage reports travel in the test-reports artifact), security.yml gains a dependency-submission job feeding the graph dependency review compares, CodeQL compiles manually with ./gradlew instead of autobuild, and Dependabot uses the gradle ecosystem. The Hilla steps use -Pvaadin.productionMode=true -Pvaadin.ci.build=true.

Rolling deployments

When the manifest’s deploy.rolling.enabled is true (or rolling: yes arrives in args), deploy.yml becomes a staged rollout instead of one job: build and push the image, deploy the new version ("green") at 0 % traffic, smoke-test its candidate URL, shift traffic in the configured steps behind a metric gate, and roll back automatically on a breach. How far it goes unattended depends on advance: auto runs the gate alone, hybrid adds a human confirmation through promote.yml on prod (auto-advancing on dev), and manual ends the run once green is deployed so a human runs promote.yml for each step. Strategy blue-green is a single cutover (steps: [100]), canary and linear use the manifest’s steps with bakeMinutes of soak, and strategy rolling keeps the single-job deploy.yml (the runtime’s native rolling update) and generates no promote.yml. The first deploy of an environment, which has no rollout outputs yet, is applied at 100 % and the staged jobs are skipped.

The rollout shell functions are runtime-specific (ECS Fargate, Cloud Run, or Argo Rollouts on EKS/GKE) and embedded in deploy.yml, promote.yml and undeploy.yml; the workflows rely on the rollout_* outputs and resource naming that Setup Java Spring Boot Platform produces. deploy.yml, promote.yml and undeploy.yml share one concurrency group per environment so a deploy, a promotion and a teardown never interleave, and undeploy.yml resets the traffic split first. Rolling disabled or absent keeps today’s single-job deploy.yml unchanged, with no promote.yml; with ci: no there are no workflows at all, hence no rolling. Because a rollout in flight must not be orphaned, replacing or updating an existing deploy.yml across this behavioural change needs an explicit yes. The report also advises who may dispatch promote.yml, since a workflow_dispatch workflow is open to anyone with write access. See Spring Boot Rolling Deployments.

Plugin

Shipped by the iru-springboot plugin of the ai-catalog marketplace (install with /plugin install iru-springboot@ai-catalog; see Plugins).

Invocation

/iru-setup-java-springboot-github-workflows
/iru-setup-java-springboot-github-workflows <args: key: value lines>

Inputs

Argument Required Description Default

stack-file

No

args key: value line giving the manifest path. Read for the cloud, the Java version, visibility, the ci: block (ci.tests, ci.pages), sonar.mode (and its organization/projectKey/hostUrl), frontend (none/hilla), and the technologies in play.

springboot-stack.yml at the repository root

integration-branch, main-branch

No

args key: value lines. Otherwise confirmed against git branch -a rather than assumed to be gitflow.

develop, main

security-dependency-review, security-codeql, security-osv, security-gitleaks

No

args key: value lines, each yes/no. Individually gate the matching job in security.yml. Any not supplied via args is asked with AskUserQuestion, except when this skill runs unattended as part of Setup Java Spring Boot's Step 8, where the default stands without asking.

yes for all four

visibility, ci-tests, pages, sonar

No

args key: value lines overriding the manifest’s visibility (public/private/internal), ci.tests (unit/full), ci.pages (yes/no) and sonar.mode (cloud/self-hosted/none). Resolution order is args, then manifest, then (for visibility) gh repo view --json visibility, then asking. Public repositories get pages: yes without asking; private/internal ones are asked, with No recommended. Values resolved by asking are offered back to the manifest; an args override applies to this run only unless the user agrees.

Manifest values

rolling, deployment-strategy, advance, steps, bake-minutes

No

args key: value lines: rolling is yes/no, deployment-strategy is rolling/blue-green/canary/linear, advance is auto/manual/hybrid, steps a comma list of percentages, bake-minutes a number. The manifest’s deploy.rolling block is the source of truth; the lines apply when there is no block or the caller passes them.

Rolling disabled

mode

No

new or existing. Informational only: mode: existing does not mean "update silently" — existing files are still asked about.

None

on-existing

No

ask, keep, update or replace. Applied to every pre-existing workflow/config file without asking (the warning is still printed).

ask

On-disk survey

Yes

The workflows must match reality, so the skill reads: the reactor’s modules and the boot directory, where report-aggregate writes, whether the Sonar plugin and properties exist (only when sonar.mode isn’t none), the Antora config and which extensions docs/package.json lists, where the API doc generators write, whether apis/messaging/ and apis/graphql-server/ hold their contracts and committed docs/package-lock.json toolchains (and whether the GraphQL one pins graphql-voyager as well as spectaql), which module carries the Hilla frontend and its package-lock.json path (only when frontend: hilla), which infra/envs/<env> directories exist, whether GitHub Environments exist, and how heavy the compose stack is.

None — derived

Existing workflow files

No

Covers build.yml, deploy.yml, promote.yml (only with rolling deployments), undeploy.yml, security.yml and .github/dependabot.yml. For each that exists, the skill warns, states where it contradicts this run’s choices, and asks (unless on-existing is set): Update (merge, keeping steps this skill doesn’t own such as a Slack notification or an extra matrix leg), Keep as is, Replace (regenerate from the template, losing customizations), or Stop.

None — user must choose

Outputs

  • .github/workflows/build.yml — a build job (checkout with full history for Sonar blame, JDK with Maven cache, mvn clean verify — or mvn clean verify -DskipITs with ci.tests: unit — the site report, the Sonar scan skipped on fork pull requests, and test reports uploaded even on failure) plus a docs job gated to main-branch pushes (renamed Build documentation and ending in an artifact upload rather than a gh-pages deploy when ci.pages: no, in which case it also drops contents: write and the .nojekyll touch). The docs job generates the Maven site and the OpenAPI/protobuf documentation, then runs the two npm-driven generators as their own steps against the toolchains committed in apis/messaging/docs and apis/graphql-server/docs — validating and rendering the AsyncAPI specification, and rendering the GraphQL SDL through SpectaQL and (optionally) Voyager. It then builds the Antora site and merges everything: a "Merge documentation" step copies the contents of every target/generated-docs directory in the reactor under doc/api/, so rest-server/, grpc-server/, graphql-server/ and messaging/ land side by side without the workflow needing to know which module produced which. The published result includes doc/api/messaging/v1/index.html and doc/api/graphql-server/v1/index.html with the Voyager graph beneath it. Both npm generators are skipped in Maven (-Dasyncapi.docs.skip=true -Dgraphql.docs.skip=true) in this job so the work isn’t done twice — the build job’s mvn clean verify still exercises the frontend-maven-plugin wiring — and each step is guarded by hashFiles(…​), so deleting a contract directory doesn’t require a workflow edit. Their loops over -async-api-v.yaml and -v.graphqls mean a new contract version needs no workflow change either.

    One detail the merge step protects by accident and would break if simplified: Voyager's page loads
    `voyager.standalone.js` and `voyager.css` by relative path, so those files must travel with `index.html`. Copying
    whole directories preserves that; copying only `index.html` files would publish a blank graph.
      When `frontend: hilla`, `ci.tests` is `full` and the web module has a `playwright.config.ts` (written by
      xref:skills/iru-setup-java-springboot-hilla.adoc[]), the `build` job also gets a `Frontend UI tests` step (installs
      Chromium and runs `npx playwright test` in the web module with `UI_SCREENSHOTS_DIR` pointing at the workspace's
      `.ui-screenshots/`) and an `Upload UI screenshots` step (`if: always()`, 7-day `ui-screenshots` artifact,
      `if-no-files-found: ignore`), per the catalog's UI contract (see
      xref:guides/building-user-interfaces.adoc[Building user interfaces]). When `.ui-quality.yml` sets `accessibility: none`, no accessibility report is collected or uploaded and no accessibility-only job or step is emitted. Both steps are dropped otherwise; with
      `ci.tests: unit` the UI tests stay a local stage so no Chromium download is paid on every run.
    * `.github/workflows/deploy.yml` — with `id-token: write`, a concurrency group per environment that never cancels
      in progress, the GitHub Environment as the approval gate, buildpack image build and push, and `tofu apply` of the
      previously produced plan file.
    * `.github/workflows/deploy.yml` when rolling deployments are enabled (and the strategy is not `rolling`) — jobs
      `build-image`, `deploy-green` (apply at 0 % traffic), `smoke-candidate` (health-check the candidate URL), `shift`
      (step through the manifest's percentages with the metric gate; omitted for `advance: manual`) and `rollback`
      (return traffic to the stable version on any failure), in the same per-environment concurrency group as
      `promote.yml` and `undeploy.yml`. Per-runtime variants authenticate and drive ECS (weighted target groups on the ALB
      listener), Cloud Run (revision tags and traffic percentages) or Argo Rollouts (`kubectl argo rollouts`).
    * `.github/workflows/promote.yml` — only with rolling deployments and a strategy other than `rolling`: a `promote`
      job that advances the rollout one step or rolls it back or aborts it, deliberately without Environment reviewers.
    * `.github/workflows/undeploy.yml` — with the typed-confirmation check, the same concurrency group as `deploy.yml`
      so the two can never run at once, and separate `stop` and `destroy` paths; with rolling deployments it resets the
      traffic split first.
    * `.github/workflows/security.yml` — a `dependency-review` job (pull requests only), on Gradle a
      `dependency-submission` job, a `codeql` job (matrix
      `java-kotlin`, plus `javascript-typescript` when `frontend: hilla`), an `osv-scanner` job (the
      `google/osv-scanner-action` reusable workflow), and a `gitleaks` job (full checkout history) — each job present
      only when its `security-*` flag is `yes`. If all four flags are `no`, the file is skipped entirely rather than
      written with no jobs.
    * `.github/dependabot.yml` — always generated: grouped weekly updates for `maven` (or `gradle`), `docker`, and `github-actions`,
      plus an `npm` entry (directory set to the real web module, normally `/boot`) whenever the manifest's `frontend`
      isn't `none`, and a further `npm` entry per documentation toolchain (`docs/`, `apis/messaging/docs/`,
      `apis/graphql-server/docs/`) that already has its own committed `package-lock.json`.
    * A report including the full table of required repository secrets, environment secrets, environment variables, and
      GitHub Environments — plus the settings that are easy to miss: GitHub Pages served from `gh-pages` (only when `ci.pages: yes`), an OIDC trust
      policy scoped to the repository *and* the environment, and (for `security.yml`) Code scanning and Dependency
      graph enabled in repository settings so CodeQL and dependency review have somewhere to report into. The report also
      states whether rolling deployments were enabled (strategy, advance, steps, bake minutes), whether the run created or updated the workflows, the action taken per file (created / updated / replaced /
      kept as is, with each mismatch for kept files), how `visibility`, `ci.tests` and `ci.pages` were resolved, and the
      Actions-minutes note for private/internal repositories.

The generated files' bytes come from template files under templates/ (and, where a template needs variant-specific notes, reference/) in .claude/skills/iru-setup-java-springboot-github-workflows/; the skill reads only the files for the variant it resolved, not the whole set, which keeps its SKILL.md small.

Execution flow

flowchart TD A["Start /iru-setup-java-springboot-github-workflows"] --> B["Step 0: Read the manifest; survey the reactor,\nSonar config, Antora setup, infra/envs,\nGitHub Environments, compose stack weight"] B --> BL["Resolve language and build tool:\njava+maven, kotlin+maven or kotlin+gradle\n(the disk wins over the manifest)"] BL --> B2["Resolve CI choices (visibility, sonar,\nci-tests, pages); announce scratch vs update"] B2 -- compose stack too heavy and ci-tests full --> C["Flag now; offer a larger runner\nor a per-module IT matrix"] B2 -- a workflow file already exists --> D{"Per file: Update /\nKeep as is / Replace / Stop?\n(or on-existing)"} D -- stop --> STOP[Stop without changing any file] D -- update, keep or replace --> E B2 --> E["Step 1: Write build.yml\n(unit vs full tests, Pages vs artifact)\n(build job + main-branch-only docs job:\nMaven site, OpenAPI/protobuf docs, AsyncAPI\nand GraphQL html, Antora, merge into ./doc)"] C --> E E --> F["Step 2: Write deploy.yml\n(OIDC, image build, plan then apply, smoke check;\nwhen rolling is enabled: Step 2b staged rollout\nwith build, deploy at 0 percent, smoke test,\nshift with metric gate, automatic rollback)"] F -- "rolling enabled and strategy is not rolling" --> F2["Step 2c: Write promote.yml\n(advance, roll back or abort a rollout;\nstate read from the cloud)"] F -- "rolling disabled or strategy rolling" --> G F2 --> G["Step 3: Write undeploy.yml\n(typed confirmation, stop vs destroy;\nresets the traffic split first when rolling)"] G --> G2["Step 3b: Write security.yml\n(dependency-review, CodeQL, OSV-Scanner,\ngitleaks - each gated by a security-* flag)\nand always write dependabot.yml"] G2 --> H["Step 4: Assemble the secrets,\nvariables, and environments table"] H --> I["Step 5: Validate — YAML parses, actionlint,\naction versions current, secret names consistent;\nMaven or Gradle commands verified locally via iru-gate-runner"] I --> J["Step 6: Report files, gaps,\nand review warnings"]

deploy.yml and undeploy.yml are deliberately never triggered as a test. The skill instead recommends a dry run of build.yml on a throwaway branch, and a first deploy.yml run against dev only, watched to completion.

Dependencies

Invokes

  • Setup Java Spring Boot Poms — Step 0 (conditional), only offered when the survey finds the pom’s (or Gradle build’s) Sonar wiring missing although sonar.mode isn’t none; the skill reports the drift and offers to re-run it rather than generating a Sonar step that is certain to fail.

Otherwise it reads springboot-stack.yml and surveys the repository, then writes workflow YAML.

Invoked by

Also runs standalone (/iru-setup-java-springboot-github-workflows).

  • iru-gate-runner — spawned in Step 5 to run the workflow’s Maven (or Gradle) commands locally first, on the principle that a workflow whose commands were never run locally is a guess.

Source

SKILL.md on GitHub — the file this page was generated from.