YAML-to-POJO for AsyncAPI v2.0/v2.6/v3.0
✅ Polymorphism ✅ Validation ✅ Lombok ✅ Collections/Map ✅ Enums with Descriptions ✅ Inheritance/Interfaces
❌ OpenAPI is not supported (AsyncAPI only)
| Specification | Status | Notes |
|---|---|---|
| AsyncAPI v2.0 / v2.6 | ✅ Full | Primary target version |
| AsyncAPI v3.0 (RC) | ✅ Experimental | Supports operations, channels, messages; payload: { schema: {...} } |
| OpenAPI 3.x | ❌ Not supported | Planned no earlier than 2026 |
💡 For OpenAPI, consider OpenAPI Generator. Yojo is exclusively focused on AsyncAPI.
All examples use valid AsyncAPI-compliant YAML — no abbreviated forms like items: { type: integer }.
YAML
listOfLongs:
type: array
items:
type: integer
format: int64
realization: ArrayList
setOfDates:
type: array
format: set
items:
type: string
format: date
realization: HashSet→ Java
private List<Long> listOfLongs = new ArrayList<>();
private Set<LocalDate> setOfDates = new HashSet<>();YAML
mapUUIDCustomObject:
type: object
format: uuid
additionalProperties:
$ref: '#/components/schemas/User'→ Java
private Map<UUID, User> mapUUIDCustomObject;YAML
Result:
type: object
enum:
- SUCCESS
- DECLINE
- error-case
x-enumNames:
SUCCESS: "Operation succeeded"
DECLINE: "Declined by policy"
error-case: "Legacy lowercase"→ Java
public enum Result {
SUCCESS("Operation succeeded"),
DECLINE("Declined by policy"),
ERROR_CASE("Legacy lowercase");
private final String value;
Result(String value) { this.value = value; }
public String getValue() { return value; }
}✅
error-case→ERROR_CASE,class→CLASS_FIELD
YAML
bigDecimalValue:
type: number
format: big-decimal
multipleOf: 0.01 # → fraction = 2
bigDecimalValue2:
type: number
format: big-decimal
multipleOf: 10.0 # → integer = 2, fraction = 1→ Java
@Digits(integer = 1, fraction = 2)
private BigDecimal bigDecimalValue;
@Digits(integer = 2, fraction = 1)
private BigDecimal bigDecimalValue2;
⚠️ digits: "integer = 2, fraction = 2"is legacy. PrefermultipleOf— it’s standards-compliant, editor-validated, and used for@Digitsinference.
YAML
polymorph:
oneOf:
- $ref: '#/components/schemas/StatusOnly' # { status: string }
- $ref: '#/components/schemas/StatusWithCode' # { status: string, code: integer }→ Java
public class PolymorphStatusOnlyStatusWithCode {
private String status; // from both
private Integer code; // only from StatusWithCode (nullable)
}Supports const in discriminator field to override default discriminator value!
YAML — для явной типизации при десериализации
Pet:
type: object
discriminator: petType
properties:
name:
type: string
petType:
type: string
Cat:
allOf:
- $ref: '#/components/schemas/Pet'
properties:
huntingSkill:
type: string
Dog:
allOf:
- $ref: '#/components/schemas/Pet'
properties:
packSize:
type: integer
# ✅ const support: discriminator value is "StickBug", not "StickInsect"
StickInsect:
description: A representation of an Australian walking stick
allOf:
- $ref: '#/components/schemas/Pet'
- type: object
properties:
petType:
const: StickBug
color:
type: string
required:
- color→ Java
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "petType", visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = Cat.class, name = "Cat"),
@JsonSubTypes.Type(value = Dog.class, name = "Dog"),
@JsonSubTypes.Type(value = StickInsect.class, name = "StickBug") // ← const value!
})
public class Pet {
private String name;
@JsonTypeId
private String petType;
}
public class Cat extends Pet {
private String huntingSkill;
}
public class Dog extends Pet {
private Integer packSize;
}
// ✅ petType field with const is NOT generated in subtype (inherited from Pet)
public class StickInsect extends Pet {
private String color; // only the new field
}✅ Jackson автоматически десериализует в нужный класс по значению дискриминатора. ✅ Поле-дискриминатор в подтипах НЕ дублируется (наследуется от базового класса). ✅ Поддержка
constпозволяет использовать значение дискриминатора, отличающееся от имени схемы.
| Attribute | YAML | → Java |
|---|---|---|
x-class-annotation |
MySchema: type: object x-class-annotation: - com.example.MyClassAnnotation - com.example.AnotherAnnotation("param") |
@MyClassAnnotation@AnotherAnnotation("param")public class MySchema { ... } |
x-field-annotation |
properties: myField: type: string x-field-annotation: - com.example.MyFieldAnnotation - com.validation.MyConstraint(value = 10) |
@MyFieldAnnotation@MyConstraint(value = 10)private String myField; |
✅ Supports schema-level and message-level class annotations, plus field-level annotations with parameters.
yojo {
configurations {
create("main") {
specificationProperties {
register("api") {
specName("test.yaml")
inputDirectory(layout.projectDirectory.dir("contract").asFile.absolutePath)
outputDirectory(layout.buildDirectory.dir("generated/sources/yojo/com/example/api").get().asFile.absolutePath)
packageLocation("com.example.api")
}
register("one-more-api") {
specName("test.yaml")
inputDirectory(layout.projectDirectory.dir("contract").asFile.absolutePath)
outputDirectory(layout.buildDirectory.dir("generated/sources/yojo/oneMoreApi").get().asFile.absolutePath)
packageLocation("oneMoreApi")
}
}
springBootVersion("3.2.0")
lombok {
enable(true)
allArgsConstructor(true)
noArgsConstructor(true)
accessors {
enable(true)
fluent(false)
chain(true)
}
equalsAndHashCode {
enable(true)
callSuper(false)
}
}
}
}
}
sourceSets {
main.java.srcDir(layout.buildDirectory.dir("generated/sources/yojo"))
}
tasks.compileJava {
dependsOn("generateClasses")
}🔗 Official Gradle Plugin
📦 GitHub
| YAML | → Java | Imports |
|---|---|---|
type: string, format: uuid |
UUID |
java.util.UUID |
type: object, format: date |
LocalDate |
java.time.LocalDate |
type: string, format: local-date-time |
LocalDateTime |
java.time.LocalDateTime |
type: object, format: date-time |
OffsetDateTime |
java.time.OffsetDateTime |
type: number, format: big-decimal, multipleOf: 0.01 |
BigDecimal |
java.math.BigDecimal, @Digits(integer = 1, fraction = 2) |
| YAML | → Java |
|---|---|
type: arrayitems: type: string |
List<String> |
type: arrayformat: setitems: type: integer |
Set<Integer> |
type: objectadditionalProperties: type: string |
Map<String, String> |
type: objectformat: uuidMapadditionalProperties: $ref: '#/components/schemas/User' |
Map<UUID, User> |
type: objectadditionalProperties: type: array format: set items: type: string |
Map<String, Set<String>> |
| Attribute | Where | Type | Example | Generation |
|---|---|---|---|---|
realization |
items, additionalProperties |
string |
realization: ArrayList |
= new ArrayList<>() |
primitive |
field | boolean |
primitive: true |
int, boolean, long |
multipleOf |
number | number |
multipleOf: 0.01 |
@Digits(integer = 1, fraction = 2) |
digits |
number | string |
digits: "integer=2,fraction=2" |
@Digits(...) (legacy) |
validationGroups |
schema | list |
validationGroups: [Create.class] |
@NotBlank(groups = {Create.class}) |
validationGroupsImports |
schema | list |
validationGroupsImports: [pkg.Validation] |
import pkg.Validation; |
validateByGroups |
schema | list |
validateByGroups: [name, email] |
annotations only on listed fields |
lombok.* |
schema/message | nested | see Gradle config | @Data, @Accessors, @AllArgsConstructor |
extends, implements |
schema/message | fromClass, fromPackage, fromInterface |
see examples | extends X implements Y |
format: interface |
schema | — | — | interface X { ... } |
format: existing |
field | name, package |
— | import, no class gen |
pathForGenerateMessage |
message.payload |
string |
io.github.events |
custom message package |
removeSchema |
message.payload |
boolean |
removeSchema: true |
skip DTO gen for $ref target |
x-enumNames |
enum | map |
SUCCESS: "Ok" |
enum + description field |
x-class-annotation |
schema, message | list |
[com.example.MyAnnotation] |
class-level annotations |
x-field-annotation |
field | list |
[com.example.MyAnnotation("value")] |
field-level annotations |
| Feature | Status | Description |
|---|---|---|
| Discriminator | ✅ Done | @JsonTypeInfo, @JsonSubTypes for polymorphism |
| @JsonTypeId on fields | ✅ Done | Add @JsonTypeId to discriminator field in subtypes |
Discriminator const support |
✅ Done | Support const in discriminator field to override default value |
| Jackson annotations | 🟡 Partial | @JsonProperty, @JsonInclude done |
| AsyncAPI spec validation | 🚧 Planned | Validate $ref, type, format, circular refs |
| Lombok extensions | 🚧 Planned | @Builder, @Singular, @SuperBuilder |
| OpenAPI 3.1 support | 🚧 Planned for Q2 2026 | Only after AsyncAPI 3.0 stabilization |
| Freemarker templates | 🚧 Planned | For enterprise customizations |
- Author: Vladimir Morozkin
- 📧 Email:
jvmorozkin@gmail.com - 📟 Telegram:
@vmorozkin - GitHub: yojo-generator
🙌 Send your contracts — I’ll make a PoC.
🐞 PRs and issues are welcome!
Distributed under the Apache License 2.0.
See LICENSE.md.
🚀 Let’s generate some code!
AsyncAPI → Java — fast, precise, zero manual work.
