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.

Example.java
// Create a Database for a DataSource...
DataSource dataSource = obtainDataSource();
Database database = Database.forDataSource(dataSource)
.build();
// ...and use it!
enum Color { BLUE, GREEN }
record Car(UUID carId, Color color) {}
List<Car> blueCars = database.queryForList("""
SELECT *
FROM car
WHERE color=?
""", Car.class, Color.BLUE);

Introduction

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

Installation

Pyranid is a single JAR, available on Maven Central.

Maven

Java 16+

<dependency>
  <groupId>com.pyranid</groupId>
  <artifactId>pyranid</artifactId>
  <version>2.0.1</version>
</dependency>

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

<dependency>
  <groupId>com.pyranid</groupId>
  <artifactId>pyranid</artifactId>
  <version>1.0.17</version>
</dependency>

Gradle

repositories {
  mavenCentral()
}

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() {{
  setJdbcUrl("jdbc:postgresql://localhost:5432/my-database");
  setUsername("example");
  setPassword("secret");
  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 {
  ACCOUNTING,
  HR
}

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

void awardAnnualRaises(DepartmentId departmentId) {
  payrollSystem.startLengthyWarmupProcess();

  // 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 {
        database.execute("""
          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)
          transaction.rollback(savepoint);
          
          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)
          sendCongratulationsEmail(employee);
      } else if(transactionResult == TransactionResult.ROLLED_BACK) {
        // Exception bubbled out?
        // Do some additional cleanup
        payrollSystem.cancelLengthyWarmupProcess();	
      }
    });   
  });
}

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