Skip to content

Basic CRUD with Records (Java)

Learn how to use Kapper with Java Records for clean, immutable data models.

📁 Source Code: View complete examples in the java-example directory.

Setup

java
// SuperHero record
public record SuperHeroRecord(UUID id, String name, String email, int age) {}

// Villain record
public record VillainRecord(UUID id, String name) {}

// Battle record
public record SuperHeroBattleRecord(
    UUID superHeroId,
    UUID villainId,
    LocalDateTime battleDate,
    LocalDateTime updatedTs
) {}

Create (Insert)

java
// Insert and get result
try (var connection = dataSource.getConnection()) {
    var hero = new SuperHeroRecord(
        UUID.randomUUID(), "Batman", "batman@dc.com", 85
    );

    var rows = kapper.execute(
        connection,
        """
        INSERT INTO super_heroes(id, name, email, age)
        VALUES (:id, :name, :email, :age)
        """,
        Map.of(
            "id", hero.id(),
            "name", hero.name(),
            "email", hero.email(),
            "age", hero.age()
        )
    );

    System.out.println("Inserted " + rows + " hero(s)");
}

Read (Select)

java
// Query single superhero
SuperHeroRecord hero = kapper.querySingle(
    SuperHeroRecord.class,
    connection,
    "SELECT * FROM super_heroes WHERE name = :name",
    Map.of("name", "Batman")
);

if (hero != null) {
    System.out.println("Found hero: " + hero.name());
}

// Query multiple superheroes
List<SuperHeroRecord> heroes = kapper.query(
    SuperHeroRecord.class,
    connection,
    "SELECT * FROM super_heroes WHERE age > :age",
    Map.of("age", 80)
);

heroes.forEach(h -> System.out.println("Hero: " + h.name() + ", Age: " + h.age()));

Update

java
// Update superhero age
int rowsUpdated = kapper.execute(
    connection,
    """
    UPDATE super_heroes
    SET age = :newAge
    WHERE name = :name
    """,
    Map.of(
        "newAge", 86,
        "name", "Batman"
    )
);

System.out.println("Updated " + rowsUpdated + " rows");

Delete

java
// Delete superhero
kapper.execute(
    connection,
    """
    DELETE FROM super_heroes
    WHERE name = :name
    """,
    Map.of("name", "Batman")
);

Complete Example

java
public class SuperHeroCrudExample {
    public static void main(String[] args) {
        var kapper = Kapper.getInstance();
        var dataSource = createDataSource();

        try (var connection = dataSource.getConnection()) {
            // Create superhero
            var batman = new SuperHeroRecord(
                UUID.randomUUID(), "Batman", "batman@dc.com", 85
            );

            var insertedRows = kapper.execute(
                connection,
                """
                INSERT INTO super_heroes(id, name, email, age)
                VALUES (:id, :name, :email, :age)
                """,
                Map.of(
                    "id", batman.id(),
                    "name", batman.name(),
                    "email", batman.email(),
                    "age", batman.age()
                )
            );

            System.out.println("Inserted " + insertedRows + " hero(s)");

            // Read hero back
            SuperHeroRecord retrievedHero = kapper.querySingle(
                SuperHeroRecord.class,
                connection,
                "SELECT * FROM super_heroes WHERE id = :id",
                Map.of("id", batman.id())
            );

            if (retrievedHero != null) {
                System.out.println("Retrieved: " + retrievedHero.name() + " (" + retrievedHero.age() + " years old)");
            }

            // Update hero
            kapper.execute(
                connection,
                "UPDATE super_heroes SET age = :age WHERE id = :id",
                Map.of("age", 86, "id", batman.id())
            );

            // Verify update
            SuperHeroRecord updatedHero = kapper.querySingle(
                SuperHeroRecord.class,
                connection,
                "SELECT * FROM super_heroes WHERE id = :id",
                Map.of("id", batman.id())
            );

            if (updatedHero != null) {
                System.out.println("Updated: " + updatedHero.name() + " is now " + updatedHero.age() + " years old");
            }

            // Clean up
            kapper.execute(
                connection,
                "DELETE FROM super_heroes WHERE id = :id",
                Map.of("id", batman.id())
            );

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private static HikariDataSource createDataSource() {
        var dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:postgresql://localhost:5432/example");
        dataSource.setUsername("user");
        dataSource.setPassword("password");
        return dataSource;
    }
}

Working with Repository Pattern

java
public class SuperHeroRepository {
    private final Kapper kapper;
    private final DataSource dataSource;

    public SuperHeroRepository(DataSource dataSource) {
        this.kapper = Kapper.getInstance();
        this.dataSource = dataSource;
    }

    public int insertHero(SuperHeroRecord hero) throws SQLException {
        try (var connection = dataSource.getConnection()) {
            return kapper.execute(
                connection,
                """
                INSERT INTO super_heroes(id, name, email, age)
                VALUES (:id, :name, :email, :age)
                """,
                Map.of(
                    "id", hero.id(),
                    "name", hero.name(),
                    "email", hero.email(),
                    "age", hero.age()
                )
            );
        }
    }

    public SuperHeroRecord findById(UUID id) throws SQLException {
        try (var connection = dataSource.getConnection()) {
            return kapper.querySingle(
                SuperHeroRecord.class,
                connection,
                "SELECT * FROM super_heroes WHERE id = :id",
                Map.of("id", id)
            );
        }
    }

    public List<SuperHeroRecord> findByAgeRange(int minAge, int maxAge) throws SQLException {
        try (var connection = dataSource.getConnection()) {
            return kapper.query(
                SuperHeroRecord.class,
                connection,
                "SELECT * FROM super_heroes WHERE age BETWEEN :minAge AND :maxAge",
                Map.of("minAge", minAge, "maxAge", maxAge)
            );
        }
    }
}

Error Handling

java
public SuperHeroRecord findHeroSafely(String name) {
    try (var connection = dataSource.getConnection()) {
        return kapper.querySingle(
            SuperHeroRecord.class,
            connection,
            "SELECT * FROM super_heroes WHERE name = :name",
            Map.of("name", name)
        );
    } catch (SQLException e) {
        System.err.println("Database error: " + e.getMessage());
        return null;
    }
}

public SuperHeroRecord createHeroWithValidation(String name, String email, int age) {
    if (name == null || name.trim().isEmpty()) {
        throw new IllegalArgumentException("Name cannot be empty");
    }

    if (email == null || !email.contains("@")) {
        throw new IllegalArgumentException("Invalid email format");
    }

    if (age < 0 || age > 10000) {
        throw new IllegalArgumentException("Age must be realistic");
    }

    try (var connection = dataSource.getConnection()) {
        var hero = new SuperHeroRecord(UUID.randomUUID(), name, email, age);

        kapper.execute(
            connection,
            """
            INSERT INTO super_heroes(id, name, email, age)
            VALUES (:id, :name, :email, :age)
            """,
            Map.of(
                "id", hero.id(),
                "name", hero.name(),
                "email", hero.email(),
                "age", hero.age()
            )
        );

        return hero;

    } catch (SQLException e) {
        if (e.getSQLState().equals("23505")) { // Unique violation
            throw new IllegalArgumentException("Hero with this name already exists: " + name);
        }
        throw new RuntimeException("Database error", e);
    }
}

Next Steps

Source Examples

Released under the Apache 2.0 License.