Pyranid Logo

A modern JDBC interface with zero dependencies.

Pyranid takes care of boilerplate and lets you focus on writing and thinking in SQL. It is not an ORM.
// Create a Database for a DataSource...
DataSource dataSource = obtainDataSource();
Database database = Database.forDataSource(dataSource)
// ...and use it!
enum Color { BLUE, GREEN }
record Car(UUID carId, Color color) {}
List<Car> blueCars = database.queryForList("""
FROM car
WHERE color=?
""", Car.class, Color.BLUE);


Why Pyranid?

Pyranid makes working with JDBC pleasant and puts your relational database first. Writing SQL "just works". Closure-based transactions are simple to reason about. Modern Java language features are supported.

No new query languages to learn. No leaky object-relational abstractions. No kitchen-sink frameworks that come along for the ride. No magic.

Pyranid is commercially-friendly Open Source Software, proudly powering production systems since 2015.

Design Goals

  • Small codebase
  • Customizable
  • Threadsafe
  • Zero dependencies
  • DI-friendly

Pyranid is built to be small and easy to understand.

Its API design aims for a minimal footprint with a high strength-to-weight ratio.

Do Zero-Dependency Libraries Interest You?

Similarly-flavored commercially-friendly OSS libraries are available.

  • Soklet is a DI-friendly HTTP 1.1 server that supports Virtual Threads
  • Lokalized enables natural-sounding translations (i18n) via an expression language


Pyranid is a single JAR, available on Maven Central.


Java 16+


Java 8+ (legacy; only critical fixes will be applied)



repositories {

dependencies {
  implementation 'com.pyranid:pyranid:2.0.1'

Direct Download

If you don't use Maven, you can drop pyranid-2.0.1.jar directly into your project. No other dependencies are required.

Example Usage

First, obtain a javax.sql.DataSource and use it to back your Database.

// Use any javax.sql.DataSource you like
DataSource dataSource = new HikariDataSource(new HikariConfig() {{
  setConnectionInitSql("SET TIME ZONE 'UTC'");

// Initialize with default configuration.
// These instances are threadsafe and intended to be shared across your app
Database database = Database.forDataSource(dataSource).build();

Then, do some work:

enum DepartmentId {

record Employee (
  UUID employeeId,
  DepartmentId departmentId,
  String name,
  BigDecimal salary,
  ZoneId timeZone,
  Locale locale,
  Instant createdAt
) {}

void awardAnnualRaises(DepartmentId departmentId) {

  // Ensure this set of operations commits or rolls back atomically.
  // A rollback occurs if an exception bubbles out
  database.transaction(() -> {
    // Pull a list of all employees in the department
    List<Employee> employees = database.queryForList("""
      SELECT *
      FROM employee
      WHERE department_id=?
      """, Employee.class, departmentId);

    // Get a reference to the transaction that's scoped to this closure
    Transaction transaction = database.currentTransaction().get();      

    // Calculate and apply a raise for everyone
    for(Employee employee : employees) {
      BigDecimal newSalary = payrollSystem.salaryWithAnnualRaise(employee));

      // Make a savepoint we can roll back to if something goes wrong
      Savepoint savepoint = transaction.createSavepoint();

      try {
          UPDATE employee
          SET salary=?
          WHERE employee_id=?
          """, newSalary, employee.employeeId());
      } catch(DatabaseException e) {
        // Detect a constraint violation and gracefully continue on
        if("salary_too_big".equals(e.getConstraint().orElse(null)) {
          // Put transaction back in good state
          // (prior to constraint violation)
          out.printf("Salary %s is too big for employee %s\n", 
            newSalary, employee.employeeId());
        } else {      
          // There must have been some other problem, bubble out
          throw e;

    // Schedule some work to be done after this transaction ends
    transaction.addPostTransactionOperation((transactionResult) -> {
      if(transactionResult == TransactionResult.COMMITTED) {
        // Successful commit?
        // Email everyone with the good news
        for(Employee employee : employees)
      } else if(transactionResult == TransactionResult.ROLLED_BACK) {
        // Exception bubbled out?
        // Do some additional cleanup

Want to see what else you can do? Start with Configuration.