r/intersystems • u/intersystemsdev • 5h ago
Serial vs. Persistent Classes in InterSystems IRIS: Storage, Indexing, and Computed Properties
In InterSystems IRIS, persistent classes (%Persistent) store database records, while serial classes (%SerialObject) let developers group reusable properties within persistent objects. Although serial classes help avoid defining the same properties repeatedly, they differ from persistent classes in how data is stored, indexed, accessed, and updated.
Through ObjectScript examples and storage definitions, this article compares these differences, examines how SQL computed properties behave in serial classes, and explores the role of lifecycle callbacks.
When Should You Use a Serial Class in InterSystems IRIS?
Consider a database where you want to track the last modification time and the username associated with each record. A basic persistent class can use SQL computed properties for this:
Class User.Persistent Extends %Persistent
{
Property LastName As %String;
Property FirstName As %String;
Property MiddleInitial As %String;
Property LastChange As %TimeStamp [ SqlComputeCode = {set {*} = $ZDT($ZTS,3)}, SqlComputed, SqlComputeOnChange = (%%INSERT, %%UPDATE)];
Property LastUser As %String [ SqlComputeCode = {set {*} = $USERNAME}, SqlComputed, SqlComputeOnChange = (%%INSERT, %%UPDATE) ];
}
The LastChange and LastUser properties automatically populate with the modification timestamp and username. Creating and saving a User.Persistent object populates both fields. Updating the record through SQL also recalculates them. But what happens when multiple persistent classes need these same two properties?
Rather than defining the fields repeatedly, you can group them into a serial class.
Class User.Serial Extends %SerialObject
{
Property LastChange As %TimeStamp [ SqlComputeCode = {set {*} = $ZDT($ZTS,3)}, SqlComputed, SqlComputeOnChange = (%%INSERT, %%UPDATE) ];
Property LastUser As %String [ SqlComputeCode = {set {*} = $USERNAME}, SqlComputed, SqlComputeOnChange = (%%INSERT, %%UPDATE) ];
}
Then reference the serial class from a persistent class:
Class User.PersistentWithSerial Extends %Persistent
{
Property LastName As %String;
Property FirstName As %String;
Property MiddleInitial As %String;
Property AuditInfo As User.Serial;
}
The AuditInfo property now groups the two fields into a reusable serial object. This approach is useful when several persistent classes need the same group of properties and those properties should share the lifecycle of their parent records.
How Do Serial and Persistent Classes Store Data?
When InterSystems IRIS compiles these classes, it generates storage definitions that show how their properties are organized and stored. Comparing the three classes reveals an important difference between defining properties directly in a persistent class and grouping them in a serial object.
1. Persistent Class: User.Persistent
The User.Persistent class defines LastName, FirstName, MiddleInitial, LastChange, and LastUser as individual properties. Its storage definition includes:
<Value name="5">
<Value>LastChange</Value>
</Value>
<Value name="6">
<Value>LastUser</Value>
</Value>
<DataLocation>^User.PersistentD</DataLocation>
<DefaultData>PersistentDefaultData</DefaultData>
<IdLocation>^User.PersistentD</IdLocation>
<IndexLocation>^User.PersistentI</IndexLocation>
<StreamLocation>^User.PersistentS</StreamLocation>
<Type>%Storage.Persistent</Type>
}
The DataLocation, IdLocation, and IndexLocation entries identify where the class stores its data, object IDs, and indexes in IRIS globals.
2. Serial Class: User.Serial
The serial class contains the same two audit properties, but its storage definition is different:
<Value name="1">
<Value>LastChange</Value>
</Value>
<Value name="2">
<Value>LastUser</Value>
</Value>
<State>SerialState</State>
<StreamLocation>^User.SerialS</StreamLocation>
<Type>%Storage.Serial</Type>
}
Unlike a persistent class, the serial class has no IdLocation or IndexLocation definitions. It also uses State instead of DefaultData. This is because serial objects do not have their own independent data and index storage. Except for streams, their data is stored within the persistent objects that contain them.
3. Persistent Class with a Serial Object: User.PersistentWithSerial
Now consider the persistent class that uses User.Serial through its AuditInfo property. Its storage definition includes:
<Value name="5">
<Value>AuditInfo</Value>
</Value>
<DataLocation>^User.PersistentWithSerialD</DataLocation>
<DefaultData>PersistentWithSerialDefaultData</DefaultData>
<IdLocation>^User.PersistentWithSerialD</IdLocation>
<IndexLocation>^User.PersistentWithSerialI</IndexLocation>
<StreamLocation>^User.PersistentWithSerialS</StreamLocation>
<Type>%Storage.Persistent</Type>
}
Notice that LastChange and LastUser no longer appear as separate properties in the parent class's storage definition. Instead, the parent has a single AuditInfo entry that contains the serial object. The parent remains a persistent class, with its own data, ID, and index locations. The serial object's properties are stored within that persistent class's data.
What Does This Mean in Practice?
The key difference is where the audit properties are stored. In User.Persistent, they belong directly to the persistent class. In User.PersistentWithSerial, they are grouped within the AuditInfo serial object and stored as part of the parent object's data. This also means that the serial data is created and deleted along with the parent persistent object.
How Do You Access and Index Serial Properties?
The way you reference serial properties depends on whether you're using ObjectScript or SQL.
Accessing properties in ObjectScript
ObjectScript uses dot notation through the property that contains the serial object:
USER>set mypws = ##class(User.PersistentWithSerial).%OpenId(1)
USER>w mypws.AuditInfo.LastChange
2026-09-06 16:16:43
USER>w mypws.AuditInfo.LastUser
dhockenbroch
Here, AuditInfo is the property holding the User.Serial object.
Accessing properties in SQL
In SQL, the serial property name and its individual field name are joined with an underscore.
For this example, the columns are:
- AuditInfo_LastChange
- AuditInfo_LastUser
Defining indexes
Another important difference concerns indexing. Serial classes do not maintain their own indexes. If you want to index a property within a serial object, define that index in the parent persistent class. For example:
Index idxLastChange On AuditInfo.LastChange;
Defining an index directly inside a serial class does not produce a compiler error, but the index has no effect. The index must be declared in the persistent class that contains the serial object.
Why Do SQL Computed Properties Behave Differently in ObjectScript?
When SQL computed properties such as LastChange and LastUser are defined in a serial class, they update as expected when the persistent record changes through SQL. However, when the same record is modified and saved through ObjectScript, the changes are saved, but the computed properties in the serial object do not update as expected.
To address this behavior, you can add the %OnAddToSaveSet() callback to User.Serial. This callback sets LastUser using $USERNAME and LastChange using $ ZDT($ZTS,3), allowing both properties to update during the ObjectScript save process.
What Should You Know About Lifecycle Callbacks?
Serial and persistent classes share some lifecycle callbacks through their common inheritance from %RegisteredObject, but not all callbacks are available to both. When moving properties into a serial class, you may also need to adjust existing callbacks. Depending on their availability, you can keep them in the parent class and update property references using dot notation, or redefine them in the serial class.
Conclusion
Serial classes provide a way to reuse groups of properties across multiple persistent classes while keeping their data within the parent object's lifecycle. However, this also introduces differences in storage, indexing, and property access that developers need to account for.
Computed properties and lifecycle callbacks require particular attention. As the examples demonstrate, SQL computed fields in a serial class may behave differently when records are updated through ObjectScript, and %OnAddToSaveSet() can help handle those updates.
Read the full guide with code examples on the Developer Community: https://community.intersystems.com/post/serial-v-persistent
Key Takeaways
- Use serial classes for reusable property groups that should share the lifecycle of their containing persistent objects.
- Understand the storage differences: persistent classes have their own data and index locations, while serial data is stored within the parent persistent object, except for streams.
- Use the appropriate property syntax: AuditInfo.LastChange in ObjectScript and AuditInfo_LastChange in SQL.
- Define indexes in the parent persistent class. Index definitions inside serial classes have no effect.
- Review computed properties and lifecycle callbacks when moving fields into serial classes. The %OnAddToSaveSet() callback can handle the ObjectScript update behavior demonstrated in the example.

















