JPA/Hibernate Schema Generation for OpenAPI

The axis2-jpa-schema module generates JSON Schema definitions from JPA annotations or Hibernate XML mappings (.hbm.xml). These schemas can be embedded in OpenAPI 3.0 specifications to document request/response bodies that map directly to database entities.

Module: modules/jpa-schema
Package: org.apache.axis2.jpa.schema

JPA Annotation Mode

The AnnotationIntrospector uses Java reflection to read Jakarta Persistence annotations from compiled @Entity classes. Unlike the HBM XML mode (which reads raw XML files and has a standalone CLI tool), annotation mode requires the entity classes to be compiled and on the classpath. Typical integration points:

  • At startup — a Spring bean or context listener scans entity classes and generates schemas on first request
  • At build time — a Maven/Gradle task that runs after compilation (e.g., in the process-classes phase)
  • In unit tests — call introspector.introspect(Product.class) directly

The introspector reads standard Jakarta Persistence annotations (@Entity, @Column, @Id, @ManyToOne, @OneToMany, etc.) and produces an EntitySchemaModel that the JpaSchemaGenerator converts to JSON Schema.

Supported Annotations

Annotation Schema Effect
@Entity Entity is eligible for introspection
@Table(name="...") Recorded in schema description
@Id Marked as required; readOnly=true in read schema
@GeneratedValue Excluded from write schema (server-managed)
@Column(nullable, length) Maps to required and maxLength
@Version Excluded from write schema (server-managed)
@Transient Excluded from all schemas
@Enumerated(STRING) Emitted as {"type":"string","enum":[...]}
@ManyToOne Emitted as {"$ref":"#/components/schemas/Entity"}
@OneToMany Emitted as {"type":"array","items":{"$ref":"..."}}

Example

@Entity
@Table(name = "PRODUCT")
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private BigDecimal productID;

    @Column(nullable = false, length = 200)
    private String name;

    @Enumerated(EnumType.STRING)
    private ProductStatus status;

    @Version
    private Long objVersion;

    @ManyToOne
    private Department department;

    @OneToMany
    private List<LineItem> lineItems;
}
AnnotationIntrospector introspector = new AnnotationIntrospector();
EntitySchemaModel model = introspector.introspect(Product.class);
ObjectNode readSchema = JpaSchemaGenerator.generateReadSchema(model);
ObjectNode writeSchema = JpaSchemaGenerator.generateWriteSchema(model);

Read vs Write Schema Generation

The generator produces two schema variants per entity:

Variant Includes Use Case
Read schema All fields (IDs as readOnly) GET response bodies
Write schema Excludes @GeneratedValue IDs, @Version, and custom audit fields POST/PUT request bodies

The generateBothSchemas() method returns both at once:

Map<String, ObjectNode> schemas = JpaSchemaGenerator.generateBothSchemas(model);
// "Product"      → read schema
// "ProductWrite" → write schema

What Gets Excluded from Write

  • @Id @GeneratedValue — server assigns the ID
  • @Version — server manages optimistic locking
  • @Transient — excluded from both read and write
  • Custom annotations (see below)

Custom Audit Annotation Support

Many enterprise codebases have project-specific annotations that mark fields as server-managed (e.g., @IgnoreChanges for audit timestamps, @CreatedBy, @LastModifiedDate). Register these with the introspector to exclude them from write schemas:

AnnotationIntrospector introspector = new AnnotationIntrospector();
introspector.addWriteExcludeAnnotation("com.example.IgnoreChanges");
introspector.addWriteExcludeAnnotation("com.example.audit.CreatedBy");

EntitySchemaModel model = introspector.introspect(Product.class);
// Fields annotated with @IgnoreChanges or @CreatedBy are now
// excluded from the write schema but present in the read schema.

Hibernate XML Mapping Mode (.hbm.xml)

For codebases that use Hibernate XML mappings instead of (or alongside) JPA annotations, the HbmXmlIntrospector parses .hbm.xml files and produces the same EntitySchemaModel:

HbmXmlIntrospector introspector = new HbmXmlIntrospector();
try (InputStream is = getClass().getResourceAsStream("/DepartmentBO.hbm.xml")) {
    EntitySchemaModel model = introspector.introspect(is, "DepartmentBO.hbm.xml");
    ObjectNode readSchema = JpaSchemaGenerator.generateReadSchema(model);
}

Supported HBM XML Elements

HBM XML Element Schema Effect
<id> with <generator> Required, readOnly, excluded from write
<version> Excluded from write schema
<property> Mapped by Hibernate type → JSON Schema type
<many-to-one> $ref to referenced entity
<set> / <list> Array of $ref
<component> Flattened with dot-notation prefix (e.g., address.city)
Nested <column not-null="true"> Maps to required

Type Mapping

Hibernate / Java Type JSON Schema
string, text {"type":"string"}
integer, int {"type":"integer","format":"int32"}
long, big_integer {"type":"integer","format":"int64"}
double, float, big_decimal {"type":"number"}
boolean, yes_no {"type":"boolean"}
timestamp, date {"type":"string","format":"date-time"}

Test Coverage

The JpaSchemaGeneratorTest class provides comprehensive tests:

  • Annotation introspection (12 tests): entity detection, ID fields, column constraints, version, transient, enums, ManyToOne, OneToMany, custom write-exclude annotations
  • Schema generation (6 tests): read includes all fields, write excludes server-managed fields, required array, relationship $refs, enum values, generateBothSchemas
  • HBM XML introspection (10 tests): parses CompanyBO and DepartmentBO (70+ field production-grade entity), verifies type mapping, relationships, collections, component flattening, nested column not-null

Relationship $ref Convention

Relationships are emitted as $ref pointers using the OpenAPI components convention:

// ManyToOne
{"$ref": "#/components/schemas/Department"}

// OneToMany
{"type": "array", "items": {"$ref": "#/components/schemas/LineItem"}}

// Write schema uses "Write" suffix
{"$ref": "#/components/schemas/DepartmentWrite"}

The caller is responsible for ensuring that referenced entity schemas are also generated and added to the OpenAPI components section. The batch generator (below) handles this automatically by processing all HBM files in a directory at once.

Batch Generation from HBM XML Directory

HbmBatchSchemaGenerator is a standalone command-line tool that scans a directory of *.hbm.xml files and produces a single OpenAPI 3.0 JSON document containing read and write schemas for every entity found. This is the recommended approach for projects with many HBM-mapped entities.

Command Line

java -cp axis2-jpa-schema.jar:jackson-databind.jar:jackson-core.jar:jackson-annotations.jar:commons-logging.jar \
  org.apache.axis2.jpa.schema.HbmBatchSchemaGenerator \
  src/main/resources \
  resources-axis2/openapi-schemas.json

Ant Integration

The batch generator follows the same pattern as Hibernate Tools' hbm2java and hbm2ddl tasks — same HBM input directory, different output artifact:

<target name="openapi-schema"
        description="Generate OpenAPI schemas from HBM XML mappings">
    <java classname="org.apache.axis2.jpa.schema.HbmBatchSchemaGenerator"
          fork="true" failonerror="true">
        <classpath>
            <fileset dir="lib" includes="axis2-jpa-schema*.jar,jackson-*.jar,commons-logging*.jar"/>
        </classpath>
        <!-- Input: directory containing *.hbm.xml -->
        <arg value="src/main/resources"/>
        <!-- Output: OpenAPI 3.0 JSON with all schemas -->
        <arg value="build/openapi-schemas.json"/>
    </java>
</target>

Output

The tool produces a valid OpenAPI 3.0 document:

{
  "openapi": "3.0.1",
  "info": {
    "title": "Generated from 146 HBM XML mappings",
    "version": "1.0.0"
  },
  "components": {
    "schemas": {
      "CompanyBO": { "type": "object", "properties": { ... } },
      "CompanyBOWrite": { "type": "object", "properties": { ... } },
      "DepartmentBO": { ... },
      "DepartmentBOWrite": { ... },
      "ProductBO": { ... },
      "ProductBOWrite": { ... }
    }
  }
}

Each entity produces two schemas:

  • EntityBO — read schema (GET responses): all fields, generated IDs marked readOnly
  • EntityBOWrite — write schema (POST/PUT requests): excludes generated IDs, version fields, and custom audit annotations

Console output reports each entity processed:

  OK   CompanyBO.hbm.xml → CompanyBO (12 fields, 2 relationships)
  OK   DepartmentBO.hbm.xml → DepartmentBO (58 fields, 8 relationships)
  OK   ProductBO.hbm.xml → ProductBO (8 fields, 1 relationships)
  OK   OrderBO.hbm.xml → OrderBO (15 fields, 3 relationships)
  SKIP HistoryData.hbm.xml (no entity found)

Generated 8 schemas (4 entities × 2 [read + write]) from 5 HBM files
Output: /path/to/resources-axis2/openapi-schemas.json

Build Pipeline Position

The schema generator reads HBM XML files directly — it does not depend on compiled Java classes, a running database, or a Hibernate SessionFactory. This means it can run:

  • Before codegen — HBM files are the source of truth; the generator reads them before Java classes are generated
  • In CI — no database connection required, so it runs in any CI environment
  • On schema change — regenerate whenever an HBM file changes; diff the output JSON to see exactly which fields or relationships changed

For projects that use Ant with Hibernate Tools, add openapi-schema to your existing build pipeline after code generation and before packaging. The schemas will reflect the same entity definitions that the generated Java code and DDL use.