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., @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.
Apache Axis2
