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-classesphase) - 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., an @AuditField marker for audit timestamps, @CreatedBy, @LastModifiedDate). Register these with the introspector to exclude them from write schemas:
AnnotationIntrospector introspector = new AnnotationIntrospector();
introspector.addWriteExcludeAnnotation("com.example.audit.AuditField");
introspector.addWriteExcludeAnnotation("com.example.audit.CreatedBy");
EntitySchemaModel model = introspector.introspect(Product.class);
// Fields annotated with @AuditField 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("/Branch.hbm.xml")) {
EntitySchemaModel model = introspector.introspect(is, "Branch.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 Library and Branch (a 50+ field 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 3 HBM XML mappings",
"version": "1.0.0"
},
"components": {
"schemas": {
"Book": { "type": "object", "properties": { ... } },
"BookWrite": { "type": "object", "properties": { ... } },
"Branch": { ... },
"BranchWrite": { ... },
"Library": { ... },
"LibraryWrite": { ... }
}
}
}
Each entity produces two schemas:
- Entity — read schema (GET responses): all fields, generated IDs marked
readOnly - EntityWrite — write schema (POST/PUT requests): excludes generated IDs, version fields, and custom audit annotations
Console output reports each entity processed:
OK Book.hbm.xml → Book (15 fields, 2 relationships) OK Branch.hbm.xml → Branch (55 fields, 8 relationships) OK Library.hbm.xml → Library (15 fields, 3 relationships) 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.
Apache Axis2
