PojoQuery is a lightweight Java library for working with relational databases. Instead of writing SQL queries in plain text, PojoQuery uses Plain Old Java Objects (POJOs) to define the shape of your result set — including which tables to join and which columns to fetch.
Unlike traditional ORMs where classes define how data is stored, PojoQuery uses classes to define what data you want to retrieve. This simple shift eliminates lazy loading, proxy objects, and session management complexity. Your POJO is essentially a type-safe SQL query.
This approach scales to rich domain models — PojoQuery has been used to port the DDD Sample cargo tracking system, demonstrating it handles complex domain-driven designs without compromise.
See this article for the full rationale behind this approach.
Query Building
- Type-safe database queries using POJOs with full IDE support
- Automatic SQL generation from POJO structure
- Convention-based relationship mapping (one-to-one, one-to-many, many-to-many)
- Custom joins via
@Link,@Join, and@JoinCondition - Fluent query API with
addWhere(),addOrderBy(),setLimit(),addJoin(),addGroupBy() - Alias resolution with curly braces
{table.field}for readable conditions findById()for convenient single-record lookups
Type-Safe Query Generation (compile-time)
@GenerateQueryannotation generates type-safe query builders- Compile-time field references with
where()andorderBy()methods - Fluent condition API:
eq(),ne(),gt(),lt(),between(),in(),like(),isNull(), etc. - Composable conditions with
and(),or(), andbegin()...end()grouping
CRUD Operations
- Insert, update, upsert, and delete with automatic multi-table inheritance handling
@NoUpdateto exclude fields from updates- Transaction support via
DB.runInTransaction() - Optimistic locking via
HasVersioninterface withStaleObjectException
Schema Generation & Migration
- Generate
CREATE TABLEstatements from entity classes withSchemaGenerator.createTables() - Generate
ALTER TABLEmigration statements withSchemaGenerator.generateMigrationStatements() - Automatic foreign key constraint generation
@Columnfor length, precision, scale, nullable, and unique constraints
Advanced Mapping
- Table-per-subclass inheritance (
@SubClasses) - Single table inheritance (
@SubClasses+@DiscriminatorColumn) - Embedded objects (
@Embedded) with optional column prefix - Dynamic columns (
@Other) for schemaless patterns - Custom SQL expressions (
@Select) and aggregation (@GroupBy) - Large objects (
@Lob) for CLOB/BLOB support - Field name mapping (
@FieldName) for column name customization @Transientto exclude fields from persistence
Performance & Flexibility
- Streaming API (
executeStreaming()) for large result sets without memory issues - No lazy loading, no proxies—predictable SQL, predictable behavior
- Multi-dialect support (MySQL, PostgreSQL, HSQLDB)
- JPA annotation compatibility (
jakarta.persistenceandjavax.persistence) - Custom type mapping via
FieldMappingandDbContextBuilder
Define POJOs that describe the data you want to retrieve. PojoQuery automatically generates the SQL with proper joins.
@Table("order")
class Order {
@Id Long id;
LocalDate orderDate;
Customer customer; // Joins on customer_id → customer.id
}
class OrderDetail extends Order {
List<OrderLine> lines; // Put association in subclass to prevent cycles
}
@Table("customer")
class Customer {
@Id Long id;
String name;
String email;
}
@Table("order_line")
class OrderLine {
@Id Long id;
Order order;
Product product; // Joins on product_id → product.id
int quantity;
BigDecimal unitPrice;
}
@Table("product")
class Product {
@Id Long id;
String name;
String sku;
}Generate the database schema from your entities:
SchemaGenerator.createTables(dataSource, OrderDetail.class, Customer.class, OrderLine.class, Product.class);PojoQuery generates proper CREATE TABLE statements with foreign keys, and queries join all tables automatically with proper quoting and aliases.
Query with a fluent API:
List<Order> orders = PojoQuery.build(OrderDetail.class)
.addWhere("{customer}.name LIKE ?", "%Acme%")
.addWhere("{lines.product}.sku = ?", "WIDGET-42")
.addOrderBy("{order}.orderDate DESC")
.setLimit(10)
.execute(dataSource);
// All data is eagerly loaded—no lazy loading surprises
for (OrderDetail order : orders) {
System.out.println(order.customer.name + " ordered on " + order.orderDate);
for (OrderLine line : order.lines) {
System.out.println(" " + line.quantity + "x " + line.product.name);
}
}Or use generated type-safe queries with @GenerateQuery:
@Table("employee")
@GenerateQuery
public class Employee {
@Id Long id;
String lastName;
BigDecimal salary;
Department department;
}
// Type-safe queries with fluent API
EmployeeQuery q = new EmployeeQuery();
List<Employee> results = q
.where().salary.gt(new BigDecimal("50000")).and().lastName.like("S%")
.orderBy().salary.desc()
.list(connection);Requirements: Java 17 or later.
Add PojoQuery to your project:
Maven:
<dependency>
<groupId>org.pojoquery</groupId>
<artifactId>pojoquery</artifactId>
<version>4.1.0-BETA</version>
</dependency>Gradle:
implementation 'org.pojoquery:pojoquery:4.1.0-BETA'For more details, see the PojoQuery docs.
Generate ALTER TABLE statements by comparing entity classes against the current database schema:
SchemaInfo schemaInfo = SchemaInfo.fromDataSource(dataSource);
List<String> migrations = SchemaGenerator.generateMigrationStatements(schemaInfo, Employee.class);
for (String ddl : migrations) {
DB.executeDDL(dataSource, ddl);
}Process results one at a time without loading everything into memory:
PojoQuery.build(Order.class)
.addOrderBy("{order}.id")
.executeStreaming(dataSource, order -> {
processOrder(order); // Called as each Order completes
});DB.runInTransaction(dataSource, connection -> {
PojoQuery.insert(connection, newOrder);
PojoQuery.update(connection, existingCustomer);
});To build PojoQuery from the source code:
- Prerequisites: Ensure you have JDK 17 or later installed.
- Clone the repository:
git clone https://github.com/martijnvogten/pojoquery.git - Navigate to the project directory:
cd pojoquery - Build with Maven Wrapper:
- On Linux/macOS:
./mvnw clean install - On Windows:
mvnw.cmd clean install
- On Linux/macOS:
This will compile the code, run tests, and install the artifact into your local Maven repository.
Contributions are welcome! Please feel free to submit issues and pull requests.
PojoQuery is released under the MIT License.