# How Jenkins Implements the Saveable Pattern for Configuration Persistence

> Discover Jenkins saveable pattern for configuration persistence. Learn how Jenkins uses Saveable interface for XML file storage, atomic operations, batching, and notifications.

- Repository: [Jenkins/jenkins](https://github.com/jenkinsci/jenkins)
- Tags: internals
- Published: 2026-06-19

---

**The Jenkins saveable pattern relies on the `hudson.model.Saveable` interface to persist configuration state as XML files under `$JENKINS_HOME`, using atomic file operations via `XmlFile`, batching via `BulkChange`, and event notifications via `SaveableListener`.**

Jenkins stores the majority of its system and job configuration as XML files on disk. The persistence mechanism is built around the `Saveable` interface, which provides a standardized contract for any component that needs to serialize its state. This article examines the implementation details from the `jenkinsci/jenkins` repository, showing how core classes like `Jenkins`, `Node`, and `JenkinsLocationConfiguration` implement reliable, thread-safe configuration persistence.

## The Saveable Interface Contract

Any component that requires durable configuration must implement `hudson.model.Saveable`. This interface defines a single method:

```java
void save() throws IOException;

```

Located in [`core/src/main/java/hudson/model/Saveable.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/model/Saveable.java), this contract is minimal by design. The implementing class is responsible for determining *how* to serialize itself, but the persistent storage location follows a convention: XML files typically reside within `$JENKENS_HOME` or subdirectories thereof.

Concrete implementations usually expose a protected `getConfigFile()` method that returns an `hudson.util.XmlFile` instance. This wrapper knows the specific path (e.g., `$JENKINS_HOME/jenkins.model.JenkinsLocationConfiguration.xml`) and handles the low-level I/O operations.

## Atomic Persistence with XmlFile

The actual write operation is delegated to `hudson.util.XmlFile`, located in [`core/src/main/java/hudson/util/XmlFile.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/util/XmlFile.java). This utility ensures that configuration files are never left in a partially written state, which is critical for server reliability.

A typical `save()` implementation follows this pattern:

```java
public synchronized void save() throws IOException {
    XmlFile config = getConfigFile();
    config.write(this);
    SaveableListener.fireOnChange(this);
}

```

The `XmlFile.write(Object)` method performs three steps atomically:

1. Marshals the object to XML using XStream.
2. Writes the content to a temporary file.
3. Renames the temporary file to the target filename.

This atomic rename guarantees that other processes never see a corrupted or incomplete configuration file. Most implementations mark the `save()` method as `synchronized` to prevent race conditions when multiple threads attempt to persist the same object simultaneously.

## Batching Updates with BulkChange

When multiple configuration changes must be committed together, individual calls to `save()` create unnecessary I/O overhead and risk leaving the system in an inconsistent state. Jenkins solves this with `hudson.BulkChange`, located in [`core/src/main/java/hudson/BulkChange.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/BulkChange.java).

While a `BulkChange` instance is open, calls to `save()` are suppressed. The configuration is written exactly once when the bulk change closes.

```java
try (BulkChange bc = new BulkChange(Jenkins.get())) {
    for (Node n : Jenkins.get().getNodes()) {
        n.setLabelString("linux");
        // Individual save() calls are no-ops here
    }
} // Atomic write happens here

```

This pattern is extensively used in [`core/src/main/java/jenkins/model/Jenkins.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/jenkins/model/Jenkins.java) when updating collections of nodes or global configuration properties.

## Notifying Configuration Changes

After a successful write, implementations must notify listeners by calling `SaveableListener.fireOnChange(this)`. This static method, defined in [`core/src/main/java/hudson/model/listeners/SaveableListener.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/model/listeners/SaveableListener.java), broadcasts the change to all registered listeners.

Listeners can react to configuration changes to reload caches, update UI components, or trigger downstream processes:

```java
SaveableListener.all().add(new SaveableListener() {
    @Override
    public void onChange(Saveable s, XmlFile file) {
        logger.info("Configuration changed: " + s.getClass().getName());
    }
});

```

## Concrete Implementation: JenkinsLocationConfiguration

The `JenkinsLocationConfiguration` class demonstrates the complete pattern in [`core/src/main/java/jenkins/model/JenkinsLocationConfiguration.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/jenkins/model/JenkinsLocationConfiguration.java). This class stores the Jenkins instance URL and follows the standard workflow:

- It extends `GlobalConfiguration` and implements `Saveable`.
- It stores the XML file at `$JENKINS_HOME/jenkins.model.JenkinsLocationConfiguration.xml`.
- Mutator methods call `save()` immediately after updating fields.

```java
public class JenkinsLocationConfiguration extends GlobalConfiguration implements Saveable {
    private String url;

    public String getUrl() { return url; }
    
    public void setUrl(String url) { 
        this.url = url; 
        save(); // Persist immediately after change
    }

    @Override
    public synchronized void save() throws IOException {
        XmlFile config = getConfigFile();
        config.write(this);
        SaveableListener.fireOnChange(this);
    }
}

```

When an administrator updates the Jenkins URL via *Manage Jenkins → Configure System*, the `setUrl()` method triggers the persistence chain, ensuring the change survives a server restart.

## Summary

- **Implement `Saveable`**: Classes declare `implements Saveable` and provide a `void save()` method to enable persistence.
- **Use `XmlFile`**: Delegate actual writes to `hudson.util.XmlFile` for atomic, XStream-based serialization that prevents file corruption.
- **Batch with `BulkChange`**: Wrap multiple configuration updates in a `BulkChange` block to reduce I/O and maintain consistency.
- **Notify listeners**: Invoke `SaveableListener.fireOnChange()` after writes to alert the system of configuration updates.
- **Thread-safety**: Mark `save()` implementations as `synchronized` to handle concurrent access safely.

## Frequently Asked Questions

### What is the Jenkins saveable pattern for configuration persistence?

The Jenkins saveable pattern is an architectural approach where configuration-aware classes implement the `hudson.model.Saveable` interface and define a `save()` method that serializes the object to XML using `hudson.util.XmlFile`. This pattern ensures atomic writes, supports batch updates via `BulkChange`, and provides notification hooks via `SaveableListener`.

### Where does Jenkins store configuration files when using the Saveable pattern?

Jenkins stores XML configuration files under the `$JENKINS_HOME` directory. The specific path is determined by each implementation's `getConfigFile()` method. For example, `JenkinsLocationConfiguration` persists to `$JENKINS_HOME/jenkins.model.JenkinsLocationConfiguration.xml`.

### How does Jenkins prevent corrupted configuration files during writes?

The `XmlFile` class in [`core/src/main/java/hudson/util/XmlFile.java`](https://github.com/jenkinsci/jenkins/blob/main/core/src/main/java/hudson/util/XmlFile.java) writes to a temporary file first, then performs an atomic rename operation. This ensures that the target file is never in a partially written state, even if the JVM crashes during the write operation.

### When should I use BulkChange in the Jenkins saveable pattern?

Use `BulkChange` when making multiple configuration changes that should be committed as a single transaction. Instantiate `BulkChange` with a try-with-resources block around the modification code. While the bulk change is open, individual `save()` calls are suppressed, and the configuration is written exactly once when the block closes, minimizing I/O overhead.