Generate OpenAPI 3.1 specifications from Akka SDK HTTP endpoint annotations at compile time.
- Zero Configuration - Works out-of-the-box with sensible defaults
- Compile-Time Generation - No runtime overhead, perfect for serverless/containers
- OpenAPI 3.1 - Latest specification with full JSON Schema support
- Automatic Schema Generation - POJOs converted to JSON schemas automatically
- Optional Unwrapping -
Optional<T>fields and return types use the inner schema - JsonValue Wrappers - Jackson
@JsonValuerecords/classes are emitted as scalar schemas - Polymorphic Schemas - Jackson
@JsonTypeInfo/@JsonSubTypestypes becomeoneOfschemas with discriminators - Composed Schemas - Real
anyOf,oneOf, andallOfcompositions are preserved - Deterministic Output - Paths and component schemas are sorted for stable diffs
- Server Path Cleanup - Optional server path prefix stripping avoids duplicated path segments
- JavaDoc Extraction - Uses your existing documentation for descriptions
- Validation - Ensures generated specs are valid before writing
Add the plugin to your pom.xml:
<build>
<plugins>
<plugin>
<groupId>sh.oso</groupId>
<artifactId>akka-openapi-maven-plugin</artifactId>
<version>1.4.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>Run:
mvn compileYour OpenAPI specification will be generated at target/openapi.yaml.
Given an Akka SDK endpoint:
/**
* Customer management endpoint.
*/
@HttpEndpoint("/customers")
public class CustomerEndpoint {
/**
* Get a customer by ID.
* @param id the customer unique identifier
* @return the customer or 404 if not found
*/
@Get("/{id}")
public Customer getCustomer(String id) {
// ...
}
/**
* Create a new customer.
*/
@Post
public Customer createCustomer(CreateCustomerRequest request) {
// ...
}
}The plugin generates:
openapi: 3.1.0
info:
title: My API
version: 1.0.0
paths:
/customers/{id}:
get:
summary: Get a customer by ID.
operationId: getCustomer
parameters:
- name: id
in: path
required: true
description: the customer unique identifier
schema:
type: string
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/Customer'
'404':
description: Not Found
/customers:
post:
summary: Create a new customer.
operationId: createCustomer
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateCustomerRequest'
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/Customer'
components:
schemas:
Customer:
type: object
properties:
id:
type: string
name:
type: string
email:
type: string
format: email
CreateCustomerRequest:
type: object
required:
- name
- email
properties:
name:
type: string
email:
type: string
format: email<plugin>
<groupId>sh.oso</groupId>
<artifactId>akka-openapi-maven-plugin</artifactId>
<version>1.4.0</version>
<configuration>
<!-- Output settings -->
<outputFile>${project.build.directory}/openapi.yaml</outputFile>
<outputFormat>yaml</outputFormat> <!-- yaml or json -->
<!-- API metadata -->
<apiTitle>${project.name}</apiTitle>
<apiVersion>${project.version}</apiVersion>
<apiDescription>${project.description}</apiDescription>
<!-- Package scanning -->
<scanPackages>
<package>com.example.endpoints</package>
</scanPackages>
<!-- Server definitions -->
<servers>
<server>
<url>https://api.example.com/v1</url>
<description>Production</description>
</server>
</servers>
<stripServerPathPrefix>true</stripServerPathPrefix>
<!-- Security schemes (apiKey only; see Security Schemes section below) -->
<security>
<securityScheme>
<schemeName>CustomAuthHeader</schemeName>
<type>apiKey</type>
<in>header</in>
<name>x-custom-auth</name>
<description>Custom authentication header</description>
</securityScheme>
</security>
<!-- Validation -->
<failOnValidationError>true</failOnValidationError>
<!-- Skip generation -->
<skip>false</skip>
</configuration>
</plugin>Declare apiKey-based security schemes via the <security> block. Each entry
becomes both a components.securitySchemes entry and a top-level security
requirement, applied to every operation:
<security>
<securityScheme>
<schemeName>CustomAuthHeader</schemeName>
<type>apiKey</type>
<in>header</in> <!-- header | query | cookie -->
<name>x-custom-auth</name> <!-- the header / query / cookie key -->
<description>Optional human-readable description</description>
</securityScheme>
</security>Generates:
security:
- CustomAuthHeader: []
components:
securitySchemes:
CustomAuthHeader:
type: apiKey
description: Optional human-readable description
name: x-custom-auth
in: headerNotes:
- Only
type: apiKeyis supported today.http,oauth2, andopenIdConnectare rejected with a clear error and tracked for follow-up. - Multiple
<securityScheme>entries become separate items in the top-levelsecurityarray. In OpenAPI semantics that is an OR (any one is sufficient), not an AND. - Set
<includeSecuritySchemes>false</includeSecuritySchemes>to suppress emission without removing the<security>block.
Types annotated with Jackson @JsonTypeInfo and @JsonSubTypes are emitted as
OpenAPI oneOf schemas with discriminator mappings:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "channel")
@JsonSubTypes({
@JsonSubTypes.Type(value = EmailNotification.class, name = "EMAIL"),
@JsonSubTypes.Type(value = SmsNotification.class, name = "SMS")
})
public sealed interface Notification permits EmailNotification, SmsNotification {
}Generates:
Notification:
discriminator:
propertyName: channel
mapping:
EMAIL: "#/components/schemas/EmailNotification"
SMS: "#/components/schemas/SmsNotification"
oneOf:
- $ref: "#/components/schemas/EmailNotification"
- $ref: "#/components/schemas/SmsNotification"Optional<T> fields and return types are unwrapped to the schema for T, so
the generated OpenAPI does not contain opaque Optional components:
public record NotificationPreferences(
Optional<Title> title,
Optional<DeviceToken> deviceToken
) {
}Jackson @JsonValue wrappers are represented as the scalar value they serialize
to, including formats for numbers, dates, UUIDs, and other known scalar types:
public record Title(@JsonValue String title) {
}
public record DeviceToken(@JsonValue long tokenId) {
}Generates:
NotificationPreferences:
type: object
properties:
title:
$ref: "#/components/schemas/Title"
deviceToken:
$ref: "#/components/schemas/DeviceToken"
Title:
type: string
DeviceToken:
type: integer
format: int64Meaningful JSON Schema compositions are preserved in the OpenAPI output:
FlexibleIdentifier:
anyOf:
- type: string
- type: integer
format: int64Nullable-only wrappers such as anyOf: [{type: string}, {type: null}] are still
simplified to the non-null schema so common Java nullable patterns stay concise.
| Annotation | OpenAPI Mapping |
|---|---|
@HttpEndpoint(path) |
Base path for all operations |
@Get, @Post, @Put, @Delete, @Patch |
HTTP methods |
Path parameters (e.g., /{id}) |
parameters[in=path] |
| Last complex type parameter | requestBody |
| Method return type | Response schema |
| JavaDoc comments | summary and description |
For additional control, use the optional custom annotations:
@HttpEndpoint("/customers")
@OpenAPITag(name = "Customers", description = "Customer management")
public class CustomerEndpoint {
@Get("/{id}")
@OpenAPISummary("Get customer by ID")
@OpenAPIResponse(status = "200", description = "Customer found")
@OpenAPIResponse(status = "404", description = "Customer not found")
public Customer getCustomer(String id) {
// ...
}
}@OpenAPISummary("...") populates the operation's summary field — a short,
single-line label shown in tools like Swagger UI.
When an endpoint returns a domain object directly, the plugin infers the response schema from the method return type:
@Get("/{customerId}")
public CustomerResponse getCustomer(String customerId) {
return service.getCustomer(customerId);
}The same inference works for asynchronous methods such as
CompletionStage<CustomerResponse>.
For low-level Akka responses, endpoint methods return
akka.http.javadsl.model.HttpResponse. That type does not carry the response body
type in the Java signature, even when the implementation uses
HttpResponses.ok(payload), so annotate the method with @OpenAPIResponseSchema:
@Get("/{customerId}")
@OpenAPIResponseSchema(CustomerResponse.class)
public HttpResponse getCustomer(String customerId) {
return HttpResponses.ok(service.getCustomer(customerId));
}This also works for asynchronous low-level responses:
@Get("/{customerId}")
@OpenAPIResponseSchema(CustomerResponse.class)
public CompletionStage<HttpResponse> getCustomer(String customerId) {
return service.getCustomer(customerId)
.thenApply(HttpResponses::ok);
}Both low-level examples produce:
responses:
"200":
description: Success
content:
application/json:
schema:
$ref: "#/components/schemas/CustomerResponse"Note: If a method returns
HttpResponseorCompletionStage<HttpResponse>without@OpenAPIResponseSchema, the generated response will have no content schema. The plugin logs a warning in this case.
- Java 17 or later
- Maven 3.6.3 or later
- Akka SDK 3.0.0 or later. The repository tracks Akka SDK 3.5.17 as the current reference version.
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
- Akka SDK - The Akka platform for building reactive applications
- Swagger Core - OpenAPI implementation for Java
- ClassGraph - Fast classpath scanner