A simple and fluent SQL query builder for Java that allows you to construct SQL queries programmatically using a type-safe, builder pattern API.
- Fluent API: Chain methods to build queries in a readable way
- Type-safe: Compile-time safety for query construction
- State Management: Prevents duplicate clauses and invalid query structures
- Aggregate Functions: Support for COUNT, SUM, MIN, MAX, and AVG
- Flexible Conditions: Rich set of WHERE clause conditions with AND/OR support
- Table Aliases: Support for table and column aliases
- JOIN Operations: Support for INNER, LEFT, RIGHT, FULL OUTER, and CROSS JOINs
import br.com.encoders.kriterion.QueryOver;
var statement = QueryOver.builder()
.selectAll()
.from("Customers")
.build();
System.out.println(statement.sql());
// Output: SELECT * FROM Customersimport br.com.encoders.kriterion.QueryOver;
import br.com.encoders.kriterion.builder.projection.Projector;
var statement = QueryOver.builder()
.select(
Projector.property("firstName"),
Projector.property("lastName"),
Projector.property("age")
)
.from("Customers")
.build();
System.out.println(statement.sql());
// Output: SELECT firstName, lastName, age FROM Customersvar statement = QueryOver.builder()
.select(
Projector.property("c.firstName").as("Name"),
Projector.property("c.lastName").as("FamilyName"),
Projector.property("c.age").as("CurrentAge")
)
.from("Customers", "c")
.build();
System.out.println(statement.sql());
// Output: SELECT c.firstName AS Name, c.lastName AS FamilyName, c.age AS CurrentAge FROM Customers c// COUNT
var countQuery = QueryOver.builder()
.select(Projector.count("c.age").as("CountAge"))
.from("Customers", "c")
.build();
// Output: SELECT COUNT(c.age) AS CountAge FROM Customers c
// SUM
var sumQuery = QueryOver.builder()
.select(Projector.sum("c.age").as("SumAge"))
.from("Customers", "c")
.build();
// Output: SELECT SUM(c.age) AS SumAge FROM Customers c
// MIN
var minQuery = QueryOver.builder()
.select(Projector.min("c.age").as("MinAge"))
.from("Customers", "c")
.build();
// Output: SELECT MIN(c.age) AS MinAge FROM Customers c
// MAX
var maxQuery = QueryOver.builder()
.select(Projector.max("c.age").as("MaxAge"))
.from("Customers", "c")
.build();
// Output: SELECT MAX(c.age) AS MaxAge FROM Customers c
// AVG
var avgQuery = QueryOver.builder()
.select(Projector.avg("c.age").as("AverageAge"))
.from("Customers", "c")
.build();
// Output: SELECT AVG(c.age) AS AverageAge FROM Customers cimport br.com.encoders.kriterion.builder.condition.Conditioner;
// Equality
var statement = QueryOver.builder()
.selectAll()
.from("Customers")
.where(Conditioner.eq("age", 18))
.build();
// Output: SELECT * FROM Customers WHERE age = 18
// Not Equal
var statement = QueryOver.builder()
.selectAll()
.from("Customers")
.where(Conditioner.neq("status", "inactive"))
.build();
// Output: SELECT * FROM Customers WHERE status <> 'inactive'// Greater than
.where(Conditioner.gt("age", 18))
// Output: WHERE age > 18
// Less than
.where(Conditioner.lt("age", 65))
// Output: WHERE age < 65
// Greater than or equal
.where(Conditioner.gte("age", 18))
// Output: WHERE age >= 18
// Less than or equal
.where(Conditioner.lte("age", 65))
// Output: WHERE age <= 65// LIKE
.where(Conditioner.like("name", "%John%"))
// Output: WHERE name LIKE '%John%'
// IN
.where(Conditioner.in("status", "active", "pending", "approved"))
// Output: WHERE status IN ("active", "pending", "approved")
// BETWEEN
.where(Conditioner.between("age", 18, 65))
// Output: WHERE age BETWEEN 18 AND 65
// IS NULL
.where(Conditioner.isNull("deletedAt"))
// Output: WHERE deletedAt IS NULL
// IS NOT NULL
.where(Conditioner.isNotNull("email"))
// Output: WHERE email IS NOT NULL// Multiple conditions with AND
var statement = QueryOver.builder()
.selectAll()
.from("Customers")
.where(
Conditioner.eq("age", 18)
.and("kind", "RED")
)
.build();
// Output: SELECT * FROM Customers WHERE age = 18 AND kind = 'RED'
// Chaining multiple AND conditions
var statement = QueryOver.builder()
.selectAll()
.from("Customers")
.where(
Conditioner.eq("age", 18)
.and("status", "active")
.and("verified", true)
)
.build();
// Output: SELECT * FROM Customers WHERE age = 18 AND status = 'active' AND verified = TRUE
// Using OR
var statement = QueryOver.builder()
.selectAll()
.from("Customers")
.where(
Conditioner.eq("status", "active")
.or("status", "pending")
)
.build();
// Output: SELECT * FROM Customers WHERE status = 'active' OR status = 'pending'
// Complex conditions with nested AND/OR
var statement = QueryOver.builder()
.selectAll()
.from("Customers")
.where(
Conditioner.eq("age", 18)
.and(Conditioner.eq("status", "active")
.or("status", "pending"))
)
.build();Kriterion supports all major SQL JOIN types, allowing you to combine data from multiple tables with flexible join conditions.
import br.com.encoders.kriterion.builder.relation.Relations;
import br.com.encoders.kriterion.builder.relation.Connections;
var statement = QueryOver.builder()
.selectAll()
.from("Customers", "c")
.join(
Relations.inner("Orders", "o")
.on(Connections.eq("c.Id", "o.CustomerId"))
)
.build();
// Output: SELECT * FROM Customers c INNER JOIN Orders o ON c.Id = o.CustomerIdReturns only rows that have matching values in both tables.
var statement = QueryOver.builder()
.selectAll()
.from("Customers", "c")
.join(
Relations.inner("Orders", "o")
.on(Connections.eq("c.Id", "o.CustomerId"))
)
.build();
// Output: SELECT * FROM Customers c INNER JOIN Orders o ON c.Id = o.CustomerIdReturns all rows from the left table and matching rows from the right table.
var statement = QueryOver.builder()
.selectAll()
.from("Customers", "c")
.join(
Relations.left("Orders", "o")
.on(Connections.eq("c.Id", "o.CustomerId"))
)
.build();
// Output: SELECT * FROM Customers c LEFT JOIN Orders o ON c.Id = o.CustomerIdReturns all rows from the right table and matching rows from the left table.
var statement = QueryOver.builder()
.selectAll()
.from("Customers", "c")
.join(
Relations.right("Orders", "o")
.on(Connections.eq("c.Id", "o.CustomerId"))
)
.build();
// Output: SELECT * FROM Customers c RIGHT JOIN Orders o ON c.Id = o.CustomerIdReturns all rows when there is a match in either left or right table.
var statement = QueryOver.builder()
.selectAll()
.from("Customers", "c")
.join(
Relations.full("Orders", "o")
.on(Connections.eq("c.Id", "o.CustomerId"))
)
.build();
// Output: SELECT * FROM Customers c FULL OUTER JOIN Orders o ON c.Id = o.CustomerIdReturns the Cartesian product of rows from both tables.
var statement = QueryOver.builder()
.selectAll()
.from("Customers", "c")
.join(
Relations.cross("Products", "p")
)
.build();
// Output: SELECT * FROM Customers c CROSS JOIN Products pYou can chain multiple JOINs in a single query:
var statement = QueryOver.builder()
.selectAll()
.from("Customers", "c")
.join(
Relations.inner("Products", "p")
.on(Connections.eq("c.Id", "p.CustomerId")),
Relations.left("Orders", "o")
.on(Connections.eq("c.Id", "o.CustomerId"))
)
.build();
// Output: SELECT * FROM Customers c INNER JOIN Products p ON c.Id = p.CustomerId LEFT JOIN Orders o ON c.Id = o.CustomerIdJOINs can be combined with WHERE conditions:
var statement = QueryOver.builder()
.select(
Projector.property("c.Name"),
Projector.property("p.ProductName"),
Projector.property("o.OrderDate")
)
.from("Customers", "c")
.join(
Relations.inner("Products", "p")
.on(Connections.eq("c.Id", "p.CustomerId")),
Relations.left("Orders", "o")
.on(Connections.eq("c.Id", "o.CustomerId"))
)
.where(
Conditioner.eq("c.active", true)
)
.build();
// Output: SELECT c.Name, p.ProductName, o.OrderDate FROM Customers c INNER JOIN Products p ON c.Id = p.CustomerId LEFT JOIN Orders o ON c.Id = o.CustomerId WHERE c.active = TRUEvar statement = QueryOver.builder()
.select(
Projector.property("c.Name"),
Projector.property("p.ProductName"),
Projector.property("o.OrderDate"),
Projector.property("s.ShipDate")
)
.from("Customers", "c")
.join(
Relations.inner("Products", "p")
.on(Connections.eq("c.Id", "p.CustomerId")),
Relations.left("Orders", "o")
.on(Connections.eq("c.Id", "o.CustomerId")),
Relations.left("Shipments", "s")
.on(Connections.eq("o.Id", "s.OrderId"))
)
.where(
Conditioner.eq("c.active", true)
)
.build();
// Output: SELECT c.Name, p.ProductName, o.OrderDate, s.ShipDate FROM Customers c INNER JOIN Products p ON c.Id = p.CustomerId LEFT JOIN Orders o ON c.Id = o.CustomerId LEFT JOIN Shipments s ON o.Id = s.OrderId WHERE c.active = TRUEThe Connections class provides operators for JOIN ON clauses:
// Equality
Connections.eq("c.Id", "o.CustomerId")
// Output: c.Id = o.CustomerId
// Not equal
Connections.neq("c.Id", "o.CustomerId")
// Output: c.Id <> o.CustomerId
// Greater than
Connections.gt("c.CreatedAt", "o.OrderDate")
// Output: c.CreatedAt > o.OrderDate
// Greater than or equal
Connections.gte("c.CreatedAt", "o.OrderDate")
// Output: c.CreatedAt >= o.OrderDate
// Less than
Connections.lt("c.CreatedAt", "o.OrderDate")
// Output: c.CreatedAt < o.OrderDate
// Less than or equal
Connections.lte("c.CreatedAt", "o.OrderDate")
// Output: c.CreatedAt <= o.OrderDateYou can also create JOINs without table aliases:
var statement = QueryOver.builder()
.selectAll()
.from("Customers")
.join(
Relations.inner("Orders")
.on(Connections.eq("Customers.Id", "Orders.CustomerId"))
)
.build();
// Output: SELECT * FROM Customers INNER JOIN Orders ON Customers.Id = Orders.CustomerIdThe main entry point for building queries.
QueryOver.builder()- Creates a new query builder instanceselectAll()- Selects all columns (SELECT *)select(Projection... projections)- Selects specific columns or expressionsfrom(String table)- Specifies the table namefrom(String table, String alias)- Specifies the table name with an aliasjoin(Relation... relations)- Adds one or more JOIN clauseswhere(Condition condition)- Adds a WHERE clausebuild()- Builds and returns aQueryStatement
Factory class for creating projections (columns to select).
Projector.property(String propertyName)- Creates a property projectionProjector.count(String propertyName)- Creates a COUNT aggregationProjector.sum(String propertyName)- Creates a SUM aggregationProjector.min(String propertyName)- Creates a MIN aggregationProjector.max(String propertyName)- Creates a MAX aggregationProjector.avg(String propertyName)- Creates an AVG aggregation
Projection Methods:
.as(String alias)- Adds an alias to the projection
Factory class for creating WHERE conditions.
Comparison Operators:
Conditioner.eq(String column, Object value)- Equality (=)Conditioner.neq(String column, Object value)- Not equal (<>)Conditioner.gt(String column, Object value)- Greater than (>)Conditioner.lt(String column, Object value)- Less than (<)Conditioner.gte(String column, Object value)- Greater than or equal (>=)Conditioner.lte(String column, Object value)- Less than or equal (<=)
Advanced Operators:
Conditioner.like(String column, String pattern)- LIKE pattern matchingConditioner.in(String column, Object... values)- IN clauseConditioner.between(String column, Object start, Object end)- BETWEEN rangeConditioner.isNull(String column)- IS NULL checkConditioner.isNotNull(String column)- IS NOT NULL check
Composite Conditions:
.and(String column, Object value)- Adds an AND condition.and(Condition condition)- Adds an AND condition with another condition.or(String column, Object value)- Adds an OR condition.or(Condition condition)- Adds an OR condition with another condition
Factory class for creating JOIN relations.
JOIN Types:
Relations.inner(String table)- Creates an INNER JOINRelations.inner(String table, String alias)- Creates an INNER JOIN with aliasRelations.left(String table)- Creates a LEFT JOINRelations.left(String table, String alias)- Creates a LEFT JOIN with aliasRelations.right(String table)- Creates a RIGHT JOINRelations.right(String table, String alias)- Creates a RIGHT JOIN with aliasRelations.full(String table)- Creates a FULL OUTER JOINRelations.full(String table, String alias)- Creates a FULL OUTER JOIN with aliasRelations.cross(String table)- Creates a CROSS JOINRelations.cross(String table, String alias)- Creates a CROSS JOIN with alias
Relation Methods:
.on(Connection connection)- Specifies the JOIN condition
Factory class for creating JOIN ON conditions.
Comparison Operators:
Connections.eq(String leftColumn, String rightColumn)- Equality (=)Connections.neq(String leftColumn, String rightColumn)- Not equal (<>)Connections.gt(String leftColumn, String rightColumn)- Greater than (>)Connections.gte(String leftColumn, String rightColumn)- Greater than or equal (>=)Connections.lt(String leftColumn, String rightColumn)- Less than (<)Connections.lte(String leftColumn, String rightColumn)- Less than or equal (<=)
The result of building a query.
QueryStatement.sql()- Returns the generated SQL string
The library automatically formats values according to their type:
- Strings: Wrapped in single quotes (
'value') - Numbers: Used as-is (
42,3.14) - Booleans: Converted to SQL boolean (
TRUE,FALSE) - Collections: Formatted as IN clause values (
("value1", "value2"))
The builder prevents invalid query structures:
- Duplicate Clauses: Attempting to add the same clause twice (e.g., calling
select()twice) will throw anIllegalStateException - State Validation: The builder tracks which clauses have been composed to ensure query validity
import br.com.encoders.kriterion.QueryOver;
import br.com.encoders.kriterion.builder.projection.Projector;
import br.com.encoders.kriterion.builder.condition.Conditioner;
import br.com.encoders.kriterion.builder.relation.Relations;
import br.com.encoders.kriterion.builder.relation.Connections;
var statement = QueryOver.builder()
.select(
Projector.property("c.id").as("CustomerId"),
Projector.property("c.name").as("CustomerName"),
Projector.avg("o.total").as("AverageOrderValue")
)
.from("Customers", "c")
.join(
Relations.left("Orders", "o")
.on(Connections.eq("c.Id", "o.CustomerId"))
)
.where(
Conditioner.gte("c.age", 18)
.and("c.status", "active")
.and(Conditioner.isNotNull("c.email"))
)
.build();
System.out.println(statement.sql());This is a Maven project. To build:
mvn clean installSee LICENSE file for details.