# How to Implement Transactional Property Changes with Rollback Capability in Owner

> Implement transactional property changes in Owner with rollback. Intercept and abort changes or batches using rollback exceptions for robust configuration management.

- Repository: [Matteo Baccan/owner](https://github.com/matteobaccan/owner)
- Tags: how-to-guide
- Published: 2026-03-07

---

**Owner provides a built-in transactional mechanism that lets you intercept property changes before they take effect and abort single changes or entire batches by throwing specific rollback exceptions.**

The Owner library (matteobaccan/owner) is a Java configuration management framework that maps properties to interfaces. When you need to validate configuration changes before they are applied or ensure atomic updates across multiple properties, you can implement transactional property changes with rollback capability using the library's listener interfaces and exception types.

## The Transactional Listener Architecture

Owner supports transactional semantics through two specialized listener interfaces that provide a **before-change hook**. Unlike standard property change listeners that fire after the value is set, these interfaces allow you to veto changes before they are committed.

### TransactionalPropertyChangeListener

The `TransactionalPropertyChangeListener` interface in [`org/aeonbits/owner/event/TransactionalPropertyChangeListener.java`](https://github.com/matteobaccan/owner/blob/main/org/aeonbits/owner/event/TransactionalPropertyChangeListener.java) defines the contract for intercepting individual property mutations:

```java
public interface TransactionalPropertyChangeListener extends PropertyChangeListener {
    void beforePropertyChange(PropertyChangeEvent event) 
        throws RollbackOperationException, RollbackBatchException;
}

```

Owner calls `beforePropertyChange` **before** writing the new value. If the method completes normally, the change proceeds. If it throws a rollback exception, Owner aborts according to the exception type.

### TransactionalReloadListener

For full configuration reloads, use `TransactionalReloadListener` in [`org/aeonbits/owner/event/TransactionalReloadListener.java`](https://github.com/matteobaccan/owner/blob/main/org/aeonbits/owner/event/TransactionalReloadListener.java):

```java
public interface TransactionalReloadListener extends ReloadListener {
    void beforeReload(ReloadEvent event) throws RollbackBatchException;
}

```

This hook fires when `reload()` is invoked or when hot-reloading detects a file change, allowing you to validate the entire new property set before it replaces the current configuration.

## Rollback Exception Types

Owner distinguishes between two levels of rollback granularity through specific exception classes in `org/aeonbits/owner/event/`:

### RollbackOperationException

Throw `RollbackOperationException` to abort **only the current property change** while allowing the rest of a batch operation to continue. The old value remains untouched, and Owner proceeds with subsequent properties.

### RollbackBatchException

Throw `RollbackBatchException` to abort the **entire batch operation**. This applies to:
- `clear()` operations
- `load(...)` calls
- `reload()` invocations
- Bulk property sets

When this exception is caught, Owner rolls back all changes that occurred during the current operation, restoring the configuration to its pre-operation state.

## Core Implementation in PropertiesManager

The orchestration logic resides in [`org/aeonbits/owner/PropertiesManager.java`](https://github.com/matteobaccan/owner/blob/main/org/aeonbits/owner/PropertiesManager.java). When you register a listener using `addPropertyChangeListener(String propertyName, PropertyChangeListener listener)`, Owner wraps it in a `PropertyChangeListenerWrapper` that records whether the listener implements `TransactionalPropertyChangeListener`:

```java
final boolean transactional = listener instanceof TransactionalPropertyChangeListener;
propertyChangeListeners.add(
    new PropertyChangeListenerWrapper(propertyName, listener, transactional));

```

During mutation operations (`setProperty`, `removeProperty`, `clear`, `load`, `reload`), Owner:
1. Builds a list of `PropertyChangeEvent`s
2. Invokes `fireBeforePropertyChange(event)` (lines 79-84 in `PropertiesManager`)
3. Catches `RollbackOperationException` to skip the single change
4. Catches `RollbackBatchException` to abort and `ignore()` the entire operation

## Practical Implementation Examples

### Validating a Single Property Range

This example guards `max.connections` to ensure values stay between 1 and 100:

```java
import org.aeonbits.owner.Config;
import org.aeonbits.owner.ConfigFactory;
import org.aeonbits.owner.event.TransactionalPropertyChangeListener;
import org.aeonbits.owner.event.RollbackOperationException;

public interface ServerConfig extends Config {
    @Key("max.connections")
    int maxConnections();
}

public class ConnectionLimitGuard implements TransactionalPropertyChangeListener {
    @Override
    public void beforePropertyChange(PropertyChangeEvent event) 
            throws RollbackOperationException {
        if (!"max.connections".equals(event.getPropertyName())) return;
        
        int newVal = Integer.parseInt(event.getNewValue().toString());
        if (newVal < 1 || newVal > 100) {
            throw new RollbackOperationException(
                "max.connections must be between 1 and 100");
        }
    }

    @Override 
    public void propertyChange(PropertyChangeEvent e) { /* post-change logic */ }
}

```

Registration and usage:

```java
ServerConfig config = ConfigFactory.create(ServerConfig.class);
config.addPropertyChangeListener("max.connections", new ConnectionLimitGuard());

// This change is rejected; old value remains
config.setProperty("max.connections", "150"); 

```

### Preventing Batch Clear Operations

To block `clear()` or bulk loads entirely:

```java
import org.aeonbits.owner.event.RollbackBatchException;

public class ImmutableConfigGuard implements TransactionalPropertyChangeListener {
    @Override
    public void beforePropertyChange(PropertyChangeEvent event) 
            throws RollbackBatchException {
        // Throwing this aborts the entire clear() or load() operation
        throw new RollbackBatchException("Configuration is immutable");
    }

    @Override 
    public void propertyChange(PropertyChangeEvent e) { }
}

```

### Validating Full Reloads

Use `TransactionalReloadListener` to validate external file changes before they are applied:

```java
import org.aeonbits.owner.event.TransactionalReloadListener;
import org.aeonbits.owner.event.ReloadEvent;

public class EssentialKeyValidator implements TransactionalReloadListener {
    @Override
    public void beforeReload(ReloadEvent event) throws RollbackBatchException {
        if (!event.getNewProperties().containsKey("essential.key")) {
            throw new RollbackBatchException("Missing required essential.key");
        }
    }

    @Override 
    public void reloadPerformed(ReloadEvent e) { }
}

```

## Summary

- **TransactionalPropertyChangeListener** provides a `beforePropertyChange` hook that runs before values are written, allowing validation and veto capability.
- **RollbackOperationException** aborts only the current property change while continuing with other properties in a batch.
- **RollbackBatchException** aborts entire operations (`clear()`, `load()`, `reload()`), rolling back all changes to the pre-operation state.
- The **PropertiesManager** class orchestrates these callbacks, wrapping listeners and managing the transaction flow during mutations.
- For full configuration reloads, use **TransactionalReloadListener** to validate entire property sets before they replace the current configuration.

## Frequently Asked Questions

### What is the difference between RollbackOperationException and RollbackBatchException?

**RollbackOperationException** aborts only the specific property change currently being processed, leaving the old value intact and allowing the rest of a batch operation to continue. **RollbackBatchException** aborts the entire operation—such as `clear()`, `load()`, or `reload()`—and rolls back all changes that occurred during that operation, restoring the configuration to its original state.

### Can I use transactional listeners with hot-reloading?

Yes. Register a `TransactionalReloadListener` instead of a standard `ReloadListener`. The `beforeReload(ReloadEvent event)` method fires when Owner detects a file change or when `reload()` is called manually. Throwing `RollbackBatchException` from this method prevents the new configuration from replacing the current one, effectively vetoing the hot-reload.

### How do I validate only specific properties while ignoring others?

Register your `TransactionalPropertyChangeListener` for a specific property name using `addPropertyChangeListener(String propertyName, PropertyChangeListener listener)`. Inside `beforePropertyChange`, check `event.getPropertyName()` to ensure you are processing the correct key. Return early for unrelated properties, or throw `RollbackOperationException` only when your specific validation logic fails.

### Where is the transactional logic implemented in the Owner source code?

The core orchestration resides in [`org/aeonbits/owner/PropertiesManager.java`](https://github.com/matteobaccan/owner/blob/main/org/aeonbits/owner/PropertiesManager.java), specifically in methods like `fireBeforePropertyChange` and the mutation methods (`setProperty`, `clear`, `load`, `reload`). The listener interfaces and exception types are defined in the `org/aeonbits/owner/event/` package, including [`TransactionalPropertyChangeListener.java`](https://github.com/matteobaccan/owner/blob/main/TransactionalPropertyChangeListener.java), [`TransactionalReloadListener.java`](https://github.com/matteobaccan/owner/blob/main/TransactionalReloadListener.java), [`RollbackOperationException.java`](https://github.com/matteobaccan/owner/blob/main/RollbackOperationException.java), and [`RollbackBatchException.java`](https://github.com/matteobaccan/owner/blob/main/RollbackBatchException.java).