Setup Java Spring Boot APIs

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

Sets up the contract-first API layer of a Spring Boot service, in Java (Maven) or Kotlin (Maven or Gradle Kotlin DSL): a single apis/ directory at the repository root holding every contract the service exposes or consumes, and the code-generation plugin executions (Maven) or tasks (Gradle) wired into whichever module consumes each contract.

Purpose

iru-setup-java-springboot-apis enforces two invariants:

  • Every contract lives under apis/ at the repository root, never inside a module’s src/. One directory is where a reviewer looks to see everything the service exposes and consumes, and it keeps a contract from being coupled to the module that happens to implement it today. apis/ is not a Maven module.

  • Generated code is never committed and never hand-edited. It lands in each module’s target/generated-sources/ (Maven) or build/generated/ (Gradle), is git-ignored, and is excluded from Checkstyle, PMD, SpotBugs (Java) or detekt and ktlint (Kotlin), JaCoCo, and Sonar. If the generated code is wrong, the fix is in the spec or the generator configuration.

The language and build tool come from springboot-stack.yml’s `project.language (java or kotlin, absent means java) and project.buildTool (maven or gradle, absent means maven; gradle is only valid with kotlin, and a manifest pairing it with java makes the skill stop and report). The contracts in apis/ are identical in all three combinations — a contract never depends on the language implementing it; only the generator changes:

Contract Kotlin + Maven Kotlin + Gradle

REST server

kotlin-spring, interfaceOnly, useSpringBoot4, useJackson3

Same options via the org.openapi.generator GenerateTask

REST client

kotlin + jvm-spring-restclient (blocking); kotlin-spring + spring-declarative-http-interface (reactive, since jvm-spring-webclient is still Jackson 2-only)

Same libraries, GenerateTask

gRPC

The Java output plus kotlinEnabled DSL builders and protoc-gen-grpc-kotlin coroutine stubs

com.google.protobuf with the kotlin builtin and the grpc and grpckt plugins

GraphQL

graphql-codegen-maven-plugin with generatedLanguage=KOTLIN

io.github.kobylynskyi.graphql.codegen task, KOTLIN

AVRO

Unchanged — Java classes called from Kotlin

com.bakdata.gradle.avro (the maintained fork of the archived com.github.davidmc24 plugin), Java output

HTML documentation

Unchanged (html2, protoc-gen-doc, npm toolchains via frontend-maven-plugin)

documentation-group tasks (generateApiDocs and parts) writing under build/generated-docs/; npm toolchains via Exec tasks

On Gradle every plugin, protoc artifact and library version goes in gradle/libs.versions.toml (resolved live, protobuf and gRPC aligned to the versions the Spring Boot BOM manages), and modules apply plugins with alias(libs.plugins.…) and no inline version, mirroring the Maven "no <version> in module poms" rule.

The setting that makes the REST generator safe to re-run is interfaceOnly=true: the generator owns the interfaces and DTOs, and the hand-written @RestController implements them, so mvn generate-sources (or the Gradle equivalent) can never overwrite real logic. Without it the generator emits controllers and every regeneration becomes a merge conflict.

Contracts are versioned in the filename and in the path inside the contract — an OpenAPI spec’s paths carry /v1/…​, a protobuf file declares package <service>.v1, an AVRO namespace ends in .v1, and a GraphQL SDL, which has no in-band version at all, is versioned by filename and by the package its types generate into — so a breaking change means a new v2 file alongside the v1 one, with both generating and both served until v1 is retired.

apis/messaging/ is the one contract directory holding two kinds of file. The AVRO schemas define the message payloads and are what avro-maven-plugin compiles into Java; an AsyncAPI specification alongside them defines the interface — which topics exist, in which direction the service uses each one, which message each carries, and on which broker. Nothing is generated from the AsyncAPI file except documentation, but without it the AVRO schemas describe messages with no discoverable topics. Keeping both in one directory is deliberate: a new event means a new .avsc and a new channel, and a reviewer should see both in the same diff.

HTML documentation is generated alongside the code — the OpenAPI html2 generator, protoc-gen-doc for protobuf, spectaql (plus an optional GraphQL Voyager schema graph) for the GraphQL SDL, and @asyncapi/html-template for messaging — into target/generated-docs/, where the build workflow publishes it to GitHub Pages next to the Antora site, so the documentation pages link to it rather than duplicating the endpoint list by hand.

GraphQL needs that treatment explicitly because graphql-codegen-maven-plugin generates code, not documentation: without a second toolchain the one contract-first API in the reactor would publish nothing. It follows the same shape as messaging — an npm toolchain pinned beside the contract in apis/graphql-server/docs/, driven from the Maven build by frontend-maven-plugin at generate-resources, with a graphql.docs.skip escape hatch for offline builds. SpectaQL is the reference someone reads to integrate (oneFile: true yields one self-contained index.html); Voyager is the interactive graph that answers how the types connect, and is optional because it roughly doubles the published bytes. Both read the SDL directly, so neither needs a running service or an introspection endpoint in CI.

The AsyncAPI documentation is produced by the official, non-Maven AsyncAPI CLI, driven from the Maven build via frontend-maven-plugin so mvn generate-resources yields it with no globally installed Node. The specification is written against the current AsyncAPI version (3.1.0 at the time of writing); the skill confirms what’s current rather than hardcoding it, since asyncapi validate reports asyncapi-latest-version informationally whenever a newer one exists. That same validation runs as its own execution ahead of generation, and it is a real gate rather than decoration: it resolves and type-checks the referenced .avsc files, so a malformed document, a $ref to a missing schema, or an .avsc that isn’t valid AVRO all fail the build instead of yielding a page with empty payload tables. An out-of-date asyncapi: version, by contrast, is reported only as information — so a new specification release can never break the build on its own.

The skill announces whether it is creating the API layer from scratch or updating an existing one (listing the contract files, documentation toolchains and generator executions that exist). For each pre-existing artifact it owns it warns and asks Update (Recommended) / Keep as is / Replace, unless on-existing pre-answers. Existing contract files are never rewritten by Update, since a contract is the service’s real API rather than a template; Replace on a contract swaps a real API definition for a placeholder, so it is only offered after saying so explicitly and is never recommended.

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-apis

Optionally with args (stack-file:, mode:, on-existing: lines), as passed by iru-setup-java-springboot.

Inputs

Argument Required Description Default

stack-file

No

args key: value line giving the manifest path. Read to determine which contracts are needed: restServer, grpcServer, graphqlServer, each entry in restClients/grpcClients/graphqlClients, and messaging.enabled. If none apply the skill reports that and stops.

springboot-stack.yml at the repository root

mode

No

args key: value line, new or existing. Informational only: it never means "update silently".

None

on-existing

No

args key: value line, ask, keep, update or replace, pre-answering the per-artifact Update / Keep as is / Replace question for scripted runs.

ask

Target module directories

Yes

Each contract generates into a specific module. A missing module is reported and its contract skipped — this skill never creates a module.

None — a precondition

Root pom.xml pluginManagement (Maven)

Yes

The generator plugin versions must already be managed there. If one is missing it is added to the root pom, never inline in a module pom.

None — a precondition

gradle/libs.versions.toml (Gradle)

Yes

Every generator plugin and library version lives in the version catalog; missing entries are added there, never inline in a module build.gradle.kts.

None — a precondition

project.language / project.buildTool (manifest)

No

Read from springboot-stack.yml to choose the Java or Kotlin generators and Maven executions or Gradle tasks.

java / maven

Outputs

  • A report stating whether the API layer was created from scratch or updated, the action taken per pre-existing artifact (created / updated / replaced / kept as is), and any gaps left in kept artifacts.

  • apis/README.md explaining the layout, which module each directory generates into, that generated code is never committed, how to regenerate, and the versioning convention.

  • apis/rest-server/<service>-api-v1.yaml — an OpenAPI 3.1 starter spec with per-field descriptions, constraints, a documented error schema with 4xx/5xx responses, and a securitySchemes entry if security is enabled.

  • apis/grpc-server/<service>_v1.proto — a starter protobuf file with a unary and a server-streaming RPC, every message, field, and RPC commented, plus the field-numbering and reserved discipline stated in-file.

  • apis/graphql-server/<service>-v1.graphqls — a starter SDL with a type, an enum, an input, a query and a mutation, every element carrying a description string, from which graphql-codegen-maven-plugin generates resolver interfaces and model types. Two configuration details are load-bearing rather than cosmetic: the generator’s default validation annotation is javax.validation.constraints.NotNull, which does not exist on Jakarta and fails compilation until overridden, and the SDL must additionally be copied onto the runtime classpath (target/classes/graphql/, via maven-resources-plugin) because Spring for GraphQL loads the schema at startup from classpath:graphql/**/ — a service missing that copy compiles cleanly and then refuses to start.

  • apis/rest-client/<name>/, apis/grpc-client/<name>/ and apis/graphql-client/<name>/ — placeholder contracts per downstream service, each prominently marked as someone else’s contract to be replaced with the real published spec. These have two consumers, not one: the client generator, and the API mock in the compose stack that Setup Java Spring Boot Testcontainers builds from the very same file. Because it is the examples and not the schemas that become the mocked responses, every client operation gets at least one request/response example pair — named after the case it represents (found, not-found, rate-limited) and covering the error responses, since the adapter’s exception translation is what most needs testing. A spec with complete schemas and no examples generates a perfectly good client and a mock with nothing to say. gRPC contracts carry their examples in a companion artifact in the same directory, since protobuf has no examples section.

  • apis/messaging/<event>-v1.avsc — AVRO schemas with a doc on the record and every field, and a default on every optional field, since a field added without one is a backward-incompatible change the registry rejects.

  • apis/messaging/<service>-async-api-v1.yaml — an AsyncAPI specification whose channel address values are the real topic names from spring.cloud.stream.bindings..destination, and whose message payloads are $ref`s to the `.avsc files rather than restatements of them. Each operation’s action is written from this service’s point of view: send is what it produces, receive what it consumes. (This is the ambiguity AsyncAPI 3 fixed — in a 2.x document subscribe meant what the service *sends, so an adapted older file is easy to invert, and an inverted action documents every arrow in the system backwards without anything failing to say so.)

  • apis/messaging/docs/package.json (plus its committed package-lock.json and a .asyncapi-analytics file that keeps the CLI from reporting usage on every build) — the pinned AsyncAPI documentation toolchain.

  • Generator executions (Maven) or tasks (Gradle) added to api/rest-server, api/grpc-server, api/graphql-server, infrastructure/clients/<name>, infrastructure/producers, and api/consumers — including the HTML documentation executions. On Gradle, also the gradle/libs.versions.toml entries and the documentation-group task names that iru-setup-java-springboot-github-workflows discovers. The AsyncAPI executions go in exactly one module (infrastructure/producers if it exists, otherwise api/consumers), since the specification describes the whole service and generating it twice would publish the same pages under two paths.

  • Contract linting recommendations or wiring: Spectral/oasdiff for OpenAPI, buf lint/buf breaking for protobuf, GraphQL Inspector or graphql-schema-linter for the SDL, the Confluent schema-registry Maven plugin’s test-compatibility for AVRO, and asyncapi validate wired into the build (with asyncapi diff recommended for breaking-change detection) for the AsyncAPI specification.

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-apis/; 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-apis"] --> B["Step 0: Read the manifest and args;\nmap each needed contract to its module"] B -- no contracts needed --> STOP["Report and stop"] B -- a target module is missing --> SKIP["Skip that contract; report it"] B --> B1["Read project.language and buildTool;\nstop if gradle is paired with java"] B1 --> B2["Announce from scratch vs update;\nper existing artifact ask\nUpdate / Keep as is / Replace\n(or apply on-existing)"] B2 --> C["Step 1: Create the apis/ tree\nand apis/README.md"] C --> D["Steps 2-3: REST server spec + Spring generator\n(interfaceOnly) and html2 docs;\ngRPC proto + protobuf plugin and protoc-gen-doc"] D --> D2["Steps 3a-3b: GraphQL SDL + codegen resolver interfaces,\nresources copy onto the runtime classpath,\nSpectaQL/Voyager docs"] D2 --> E["Steps 4-5a: one client contract and generator\nper REST/gRPC/GraphQL client"] E --> F["Step 6: AVRO schemas + avro-maven-plugin\nin producers and/or consumers"] F --> F1["Step 6a: AsyncAPI spec at the current version —\nchannels, send/receive actions,\n$refs to the .avsc files"] F1 --> F2["Step 6b: pin the npm toolchain and wire\nasyncapi validate + html-template\ninto one module via frontend-maven-plugin"] F2 --> F3{"kotlin + gradle?"} F3 -- yes --> F4["Step 6c: version catalog entries and\nGradle tasks for every generator"] F3 -- no --> G F4 --> G["Step 7: Verify via iru-gate-runner —\ngenerate-sources or gradle build,\nthen compile"] G -->|"generator produced nothing,\nwrong package, or empty\nAsyncAPI payload tables"| H["Fix inputSpec / sourceDirectory,\nthe .avsc $ref, or the\nconfiguration schema"] H --> G G --> I["Step 8: Confirm generated output is\ngit-ignored and excluded from analysis\n(detekt/ktlint for Kotlin)"] I --> J["Step 9: Add or recommend contract linting"] J --> K["Step 10: Report actions per artifact, contracts,\ngenerators, and placeholder-spec warnings"]

Dependencies

Invokes

None. It reads springboot-stack.yml and the module poms or build.gradle.kts files, and writes contracts and generator configuration. It mentions iru-setup-java-springboot-pom (which must already have created the modules and managed plugin versions) and iru-setup-java-springboot-github-workflows (which discovers the Gradle documentation tasks) only as precondition and downstream consumer, not as calls.

Invoked by

Also runs standalone (/iru-setup-java-springboot-apis) to add a contract to an existing service.

  • iru-gate-runner — spawned for Step 7’s generate-sources and compile runs.

Source

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