Exceptions

Table of contents

  1. Hierarchy
  2. JaloquentException
  3. StorageException
  4. ValidationException
  5. TransactionException
  6. ModelNotFoundException
  7. Best practices
    1. Do not expose raw exceptions to API callers
    2. Layer your catches from specific to general
    3. Wrapping in unchecked exceptions

Hierarchy

All Jaloquent exceptions extend JaloquentException, which itself extends Exception. Every subclass logs itself at ERROR level via SLF4J on construction.

Exception
  └── JaloquentException
        ├── StorageException
        │     └── TransactionException
        ├── ValidationException
        └── ModelNotFoundException

JaloquentException

The base for all library exceptions. Catch this type when you do not care about the specific failure mode.

try {
    repo.save(model);
}
catch (JaloquentException e) {
    // handles StorageException, ValidationException, ModelNotFoundException
    log.error("Persistence failed", e);
}

Constructors:

Constructor Use case
JaloquentException(String message) Simple message
JaloquentException(String message, Throwable cause) Wraps another exception
JaloquentException(Throwable cause) Re-throws without adding a message

StorageException

Thrown when a persistence or I/O operation fails — for example, when a JDBC query throws a SQLException, when a DataStore implementation encounters disk I/O problems, or when a bulk operation partially fails.

try {
    repo.save(model);
    repo.deleteAll(ids);
}
catch (StorageException e) {
    // the underlying I/O failure is available via getCause()
    log.error("Storage operation failed: {}", e.getMessage(), e.getCause());
}

ValidationException

Thrown when a model or input fails a validation check before a persistence operation is attempted. Use this to signal semantic errors — for example, a required field is blank, or a value is out of an acceptable range.

try {
    model.validate(); // your custom validation method
    repo.save(model);
}
catch (ValidationException e) {
    // return HTTP 422 to the caller — the data is invalid
    response.sendError(422, e.getMessage());
}

TransactionException

A subclass of StorageException raised when a transaction lifecycle operation fails — beginTransaction(), commitTransaction(), or rollbackTransaction().

try (Transaction tx = repo.transaction()) {
    repo.save(model);
    tx.commit();
}
catch (TransactionException e) {
    // transaction-specific failure — e.g. connection dropped mid-commit
    log.error("Transaction failed: {}", e.getMessage(), e.getCause());
}
catch (StorageException e) {
    // general storage failure, including store not supporting transactions
    log.error("Storage failed: {}", e.getMessage(), e.getCause());
}

ModelNotFoundException

Thrown when a lookup operation requires a result but none is found — for example, in find(id) variants that are expected to return a non-null model.

try {
    Player player = repo.findOrFail("unknown-id"); // your helper wrapping repo.find()
}
catch (ModelNotFoundException e) {
    // return HTTP 404 to the caller
    response.sendError(404, e.getMessage());
}

The standard repo.find(id) returns Optional<T> and never throws ModelNotFoundException. Throw it yourself when you need to signal that a required resource is absent.


Best practices

Do not expose raw exceptions to API callers

SLF4J logs the exception on construction. Catch JaloquentException, map it to an appropriate response, and do not forward the raw exception or its message to external clients.

// CORRECT — map to a safe API response
catch (StorageException e) {
    return Response.serverError().entity("Storage error").build();
}

// WRONG — leaks internal details
catch (StorageException e) {
    return Response.serverError().entity(e.getMessage()).build();
}

Layer your catches from specific to general

try {
    repo.save(model);
}
catch (ValidationException e) {
    // 422 — invalid input
}
catch (ModelNotFoundException e) {
    // 404 — referenced entity missing
}
catch (StorageException e) {
    // 503 — storage is unavailable or failed
}
catch (JaloquentException e) {
    // 500 — unexpected library error
}

Wrapping in unchecked exceptions

If you prefer unchecked exception handling, wrap at the service layer:

public Player loadPlayer(String id) {
    try {
        return repo.find(id).orElseThrow(() ->
            new ModelNotFoundException("Player not found: " + id)
        );
    }
    catch (JaloquentException e) {
        throw new RuntimeException("Persistence failure", e);
    }
}