Skip to content

yojo-generator/generator

Repository files navigation

🚀 Yojo — AsyncAPI → Java DTO Generator

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)

Yojo Banner

Maven Central Gradle Plugin Portal JDK 17+
License AsyncAPI 2.0+ Documentation
Javadocs GitHub issues GitHub closed issues GitHub pull requests Status


⚠️ Important: Specification Support

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.


🧾 Usage Examples (valid YAML ↔ Java)

All examples use valid AsyncAPI-compliant YAML — no abbreviated forms like items: { type: integer }.


1. Collections — correct items structure

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<>();

2. Map with UUID keys

YAML

mapUUIDCustomObject:
  type: object
  format: uuid
  additionalProperties:
    $ref: '#/components/schemas/User'

Java

private Map<UUID, User> mapUUIDCustomObject;

3. Enum with descriptions (x-enumNames)

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-caseERROR_CASE, classCLASS_FIELD


5. multipleOf@Digits (preferred over digits)

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. Prefer multipleOf — it’s standards-compliant, editor-validated, and used for @Digits inference.


6. Polymorphism (oneOf, allOf)

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)
}

7. Inheritance & Interfaces (complete cases)

7.1 Simple inheritance

YAML

UserDto:
  type: object
  extends:
    fromClass: BaseEntity
    fromPackage: com.my.base
  properties:
    name: { type: string }

class UserDto extends BaseEntity { private String name; }

7.2 Override in message

YAML

CreateUserMessage:
  payload:
    $ref: '#/components/schemas/UserDto'
    extends:
      fromClass: BaseEvent
      fromPackage: com.my.events

class CreateUserMessage extends BaseEvent { /* fields from UserDto */ }
(⚠️ UserDto is not generated if UserDto == BaseEvent)

7.3 extends: SchemaName — skip field duplication

MyDto:
  type: object
  $ref: './User.yaml#/components/schemas/User'
  extends:
    fromClass: User
    fromPackage: com.example.common

class MyDto extends User { }no field duplication.


8. realization — Collections and Maps

Attribute YAML → Java
realization
users:  type: array  items:    $ref: '#/components/schemas/User'  realization: LinkedList
private List<User> users = new LinkedList<>();
cache:  type: object  realization: HashMap  additionalProperties:    type: string
private Map<String, String> cache = new HashMap<>();
Supported values ArrayList, LinkedList, HashSet, HashMap, LinkedHashMap

9. format: existing, pathForGenerateMessage, removeSchema

Attribute YAML → Java
format: existing
user:  type: object  format: existing  name: User  package: com.my.domain
private User user; + import com.my.domain.User;
pathForGenerateMessage
RequestDto:  payload:    pathForGenerateMessage: 'io.github.events'    $ref: '#/components/schemas/Dto'
class generated in .../io/github/events/RequestDto.java
removeSchema: true
payload:  $ref: '#/components/schemas/Temp'  removeSchema: true
schema Temp is not generated, only fields in message

10. Interfaces

Type YAML → Java
Marker
Marker:  type: object  format: interface
public interface Marker {}
With methods
UserService:  type: object  format: interface  imports:    - com.my.dto.User  methods:    createUser:      description: "Creates a new user"      definition: "User createUser(String email)"
public interface UserService {    /** Creates a new user */    User createUser(String email);}

📦 Integration: YOJO Gradle Plugin (recommended)

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 Mapping

Scalar Types

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)

Collections and Maps

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

🧰 Supported Attributes (per Dictionary.java)

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

🗺 Roadmap (2025–2026)

Feature Status Description
Jackson annotations ✅ In progress @JsonProperty, @JsonFormat, @JsonInclude
AsyncAPI spec validation ✅ In progress Validate $ref, type, format, circular refs
Lombok extensions ✅ In progress @Builder, @Singular, @SuperBuilder
OpenAPI 3.1 support 🚧 Planned for Q2 2026 Only after AsyncAPI 3.0 stabilization
Freemarker templates 🚧 Planned For enterprise customizations

📬 Contact

🙌 Send your contracts — I’ll make a PoC.
🐞 PRs and issues are welcome!


⚖️ License

Distributed under the Apache License 2.0.
See LICENSE.md.


🚀 Let’s generate some code!
AsyncAPI → Java — fast, precise, zero manual work.