How Jenkins Handles Job Configuration Persistence with XStream

Jenkins persists job configurations to XML files using a hardened XStream wrapper (XStream2) and a file abstraction layer (XmlFile), with model objects implementing the Saveable interface to trigger serialization to <job-dir>/config.xml.

Jenkins stores every job configuration in a config.xml file within the job's directory on the master's file system. The serialization and deserialization of these XML documents are handled by the XStream library, wrapped in Jenkins-specific helper classes to ensure security and consistency. Understanding how the jenkinsci/jenkins core implements this persistence mechanism is essential for plugin developers and administrators troubleshooting configuration issues.

The XStream2 Wrapper: Secure Serialization Core

At the heart of Jenkins persistence lies hudson.util.XStream2, a thin wrapper around the XStream library located in core/src/main/java/hudson/util/XStream2.java. This class configures a secure XStream instance by registering the allowed class hierarchy, type aliases, and custom converters.

The framework exposes this configured instance as a singleton through Jenkins.XSTREAM2, ensuring that all operations throughout the application use the same security settings and converters. When serializing objects, XStream2 applies whitelisting filters that prevent arbitrary object injection attacks.

XmlFile: The XML Persistence Layer

While XStream2 handles object-to-XML conversion, the actual file operations are managed by hudson.XmlFile (core/src/main/java/hudson/XmlFile.java). An XmlFile object is constructed with a reference to the target file and the specific XStream instance that should perform the serialization and deserialization.

This separation allows the persistence layer to remain agnostic of the specific serialization strategy while ensuring that the same security-hardened XStream2 instance is used consistently across all read and write operations.

The Saveable Interface and Job Persistence

Most Jenkins model objects implement the hudson.model.Saveable interface, which defines the contract for objects that can persist themselves. The default implementation of Saveable.save() delegates to XmlFile, creating a standardized persistence pattern across the codebase.

Saving Job Configuration

The hudson.model.Job class (and subclasses like FreeStyleProject) inherits from hudson.model.AbstractItem, which implements Saveable. When a job's configuration changes, Jenkins calls Job.save(), which creates an XmlFile pointing at <job-dir>/config.xml and writes the job object using the shared XStream2 instance.

The persistence flow follows this path:


Job.save()
 └─> new XmlFile(configFile, Jenkins.XSTREAM2)
       └─> XmlFile.write(this)  → XStream2.toXML(this) → writes <job>/config.xml

Loading Jobs at Startup

During Jenkins startup, the system scans each job directory and reconstructs objects using the Job.load() method or constructors. This process reads config.xml via XmlFile.read(), which internally calls XStream2.fromXML(). The XStream instance safely reconstructs the job object, applying the same security filters used during writing.

The loading flow mirrors the save operation:


Job.load()
 └─> new XmlFile(configFile, Jenkins.XSTREAM2)
       └─> XmlFile.read() → XStream2.fromXML() → re-creates the Job instance

Security Hardening for XStream Deserialization

Because XStream can be vulnerable to arbitrary object injection, Jenkins hardens the serialization pipeline through multiple layers of defense.

Class Filtering: A custom ClassFilter (located in jenkins/util/xstream/ClassFilter.java) whitelists only Jenkins-specific classes. The XStream2 class registers this filter automatically via XStream2.setClassLoaderFilter, ensuring that deserialization attempts with unauthorized classes are blocked before they can execute.

Exception Handling: The XmlFile helper catches XStreamException during read operations and re-wraps it as an IOException. This prevents internal XStream details from leaking through error messages while maintaining clear failure indicators for administrators.

Practical Code Examples

The following examples demonstrate how Jenkins core and plugins interact with the persistence framework.

Saving a job configuration (simplified from hudson.model.Job):

public void save() throws IOException {
    XmlFile config = new XmlFile(getConfigFile(), Jenkins.XSTREAM2);
    config.write(this);               // uses Jenkins.XSTREAM2 to serialize
}

Loading a job configuration at startup:

public static Job<?,?> load(File jobDir) throws IOException {
    File cfg = new File(jobDir, "config.xml");
    XmlFile xml = new XmlFile(cfg, Jenkins.XSTREAM2);
    return (Job<?,?>) xml.read();     // deserialises using the secured XStream instance
}

Custom XStream usage in a plugin:

XStream2 xs = new XStream2();                // gets the same security filter as the core
xs.alias("my-plugin-config", MyConfig.class);
String xml = xs.toXML(myConfig);             // serialize plugin-specific data
MyConfig cfg = (MyConfig) xs.fromXML(xml);   // deserialize safely

Summary

  • Jenkins persists jobs to <job>/config.xml using a combination of XStream2 for serialization and XmlFile for file operations.
  • hudson.util.XStream2 (core/src/main/java/hudson/util/XStream2.java) provides a secure, singleton XStream instance available via Jenkins.XSTREAM2.
  • The Saveable interface standardizes persistence across model objects, with hudson.model.Job delegating to XmlFile for actual XML read/write operations.
  • Security is enforced through a ClassFilter that whitelists allowed classes, protecting against deserialization attacks.
  • Loading at startup uses the same secure pipeline as saving, ensuring configuration integrity across Jenkins restarts.

Frequently Asked Questions

Where does Jenkins store job configuration files?

Jenkins stores each job's configuration in a config.xml file located inside the job's directory on the master's file system (typically under $JENKINS_HOME/jobs/<job-name>/). This XML file contains the serialized state of the job object, produced by the XStream2 instance when Saveable.save() is invoked.

How does Jenkins secure XStream deserialization?

Jenkins secures XStream through a custom ClassFilter located in jenkins/util/xstream/ClassFilter.java that whitelists only approved classes. The XStream2 wrapper automatically registers this filter, and the XmlFile class wraps exceptions to prevent information leakage. This prevents arbitrary object injection attacks while allowing legitimate Jenkins model objects to deserialize normally.

Can plugins use XStream2 for custom configuration persistence?

Yes. Plugins can instantiate XStream2 directly to obtain a security-hardened XStream instance that inherits the same class filtering as the core Jenkins installation. Plugins should use xs.alias() to register custom type aliases and follow the same pattern of wrapping file operations with XmlFile to ensure consistent error handling and security enforcement.

What happens if a config.xml file is corrupted during Jenkins startup?

If XmlFile.read() encounters an invalid XML structure or a class that fails the security filter, it throws an IOException (wrapping the underlying XStreamException). Depending on the severity and location, Jenkins may skip loading that specific job, log the error, or in critical cases, fail to start. Administrators should check the Jenkins system log for specific XStream unmarshalling errors when troubleshooting configuration load failures.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →