web
You’re offline. This is a read only version of the page.
close
Skip to main content

Announcements

No record found.

News and Announcements icon
Community site session details

Community site session details

Session Id :

Pack & Unpack in X++

HardikPatel523 Profile Picture HardikPatel523 233

The Feature Everyone Uses… But Nobody Explains Properly

Ever opened an AX batch job, service, or RunBase class and saw methods called `pack()` and `unpack()`?
And then your brain immediately said:
“Cool… no idea what this black magic is.”
Welcome to the club 🤝
Most developers first encounter `pack/unpack` during debugging production issues at 11:47 PM while a batch job silently refuses to restore values after restart.And somewhere in the code, there’s this mysterious line:
return [#CurrentVersion, variable1, variable2];
Looks innocent.
Destroys confidence.
Let’s finally make this thing simple.

Why Does Pack & Unpack Even Exist?

Imagine this:
You start a batch job in Dynamics AX / D365FO.
The server says:
“Okay boss, I’ll execute this later.”
But wait…
How will the system remember:
  • Selected customer
  • Date range
  • Filters
  • User selections
  • Execution settings
after hours…
or even after server restart?
That’s exactly where `pack()` and `unpack()` come in.
They help Dynamics:
  • Save object state
  • Store parameters
  • Restore values later
Basically:
👉 `pack()` = Save current state
👉 `unpack()` = Restore saved state
Simple.
Pack & Unpack are basically “Save Game” and “Load Game” for your X++ class 🎮
Once you understand this, everything starts making sense.

Wait… What Is Serialization?

Now comes the important word developers love throwing around in meetings 😄
Serialization = Converting Object Data Into Storable Format
Your class variables normally live in memory.
Example:
custAccount = "C0001";
fromDate    = 01\01\2026;
But memory disappears:
  • after batch execution
  • after server restart
  • after session ends
So Dynamics converts this data into a storable format.
That process is called: Serialization
In X++, this usually becomes a container.
Example:
[1, "C0001", 01\01\2026]
That packed container can now be:
  • Stored in database
  • Transferred across tiers
  • Restored later
And when AX rebuilds the object back from that stored data…
That is called: Deserialization
Which is basically what `unpack()` does.

Understanding It Like Real Life

Imagine Swiggy 🍔
You place an order:
  • Burger
  • Fries
  • Coke
Now delivery guy cannot remember everything manually.
So the app packs all details into one package:
  • Address
  • Items
  • Payment
  • Instructions
Later…
When delivery starts, Swiggy unpacks the details and restores the order.
That’s exactly what AX does with batch jobs and dialogs.

Another Analogy You’ll Never Forget

Think of serialization like vacuum packing clothes before travel ✈️
Your clothes are:
  • Real objects
  • Spread everywhere
  • Difficult to carry directly
So you compress everything into one travel bag.
That bag is the serialized format.
Later you open the bag and restore everything.
That’s deserialization.
Simple.
Beautiful.
Slightly less scary now.

The Real-Life Developer Pain

You create a beautiful batch job.
Testing works perfectly.
You schedule it.
Next morning:
  • Filters gone
  • Values reset
  • Dialog empty
  • Users angry
  • Architect suddenly online at 6 AM
Reason?
You forgot to properly pack/unpack values 😭

What Actually Happens Internally?

Dynamics serializes your class values into a container.
That container gets stored in database/system tables.
Later, system recreates the class and restores values.
Think of it like:
  • compressing data into a suitcase
  • storing it
  • reopening later

How It Looks Internally

Suppose your packed method returns:
[2, "WH-001", "RAW-MAT", true, NoYes::Yes]
AX stores this serialized data internally for batch persistence.
In tables like batch-related framework tables, the value may look like:
  • Binary data
  • Encoded container data
  • Serialized blob
Not very human readable 😅
Something conceptually like:
0xA102B34F....
or internally interpreted container values.
Which means:
  • AX knows how to restore it
  • Humans usually don’t enjoy reading it
And yes…
Many developers first discover serialization while staring at weird binary-looking values in SQL and questioning career choices.

Technical Breakdown

`pack()` Method

Used to store class variables into a container.
Example:
public container pack()
{
    return [#CurrentVersion, custAccount, fromDate, toDate];
}

This container is what AX stores.

`unpack()` Method

Used to restore values back into variables.
public boolean unpack(container _packedClass)
{
    Version version = conPeek(_packedClass, 1);
    switch(version)
    {
        case #CurrentVersion:
            [version, custAccount, fromDate, toDate] = _packedClass;
            break;
        default:
            return false;
    }
    return true;
}

Why Versioning Matters So Much

This is where senior developers become dangerous 😎
Today your class has:
  • Customer
  • Date
Tomorrow business says:
“Add warehouse, site, region, sales pool, delivery mode, 14 more filters…”
Now old packed data breaks.
That’s why versioning exists.

Advanced Example (Real Project Scenario)

Let’s take a scenario almost every D365FO developer eventually touches:

Sales Order Posting Cleanup Utility

Suppose you build a batch utility to clean old records from `SalesParmTable`.
Why?
Because over time:
  • posting parameter tables grow huge
  • canceled posting records remain forever
  • failed posting attempts pile up
  • database starts carrying historical junk nobody uses
And one fine day…
SQL performance drops and suddenly everyone becomes interested in cleanup jobs 😄
So you create a batch utility for cleaning old `SalesParmTable` records.
User selects:
  • retain days
  • delete only canceled postings
  • simulation mode
  • cleanup by company
Now this becomes a perfect real-world example for `pack/unpack`.
Because cleanup utilities are:
  • batch-based
  • scheduled
  • modified frequently
  • highly dependent on persistence

Version 1 — Initial Release

Initially business only wanted:
  • retain days
  • company filter
Simple times.
#define.CurrentVersion(1)

#localmacro.CurrentList
    retainDays,
    dataAreaId
#endmacro

class SalesParmCleanupBatch extends RunBaseBatch
{
    int         retainDays;
    DataAreaId  dataAreaId;

    public container pack()
    {
        return [#CurrentVersion, #CurrentList];
    }

    public boolean unpack(container _packedClass)
    {
        Version version = conPeek(_packedClass, 1);

        switch(version)
        {
            case #CurrentVersion:
                [version, #CurrentList] = _packedClass;
                break;

            default:
                return false;
        }

        return true;
    }
}
Everything works.
Job scheduled.
Developers happy.
For about 2 sprints 😄

Version 2 — Business Enhancement

Then comes:
“Just one small change…”
Now business wants:
  • simulation mode
  • delete only canceled posting records
Because apparently every requirement reproduces itself overnight.
Now your class evolves.
#define.CurrentVersion(2)

#localmacro.CurrentList
    retainDays,
    dataAreaId,
    simulateOnly,
    deleteCanceledOnly
#endmacro

class SalesParmCleanupBatch extends RunBaseBatch
{
    int         retainDays;
    DataAreaId  dataAreaId;
    NoYes       simulateOnly;
    NoYes       deleteCanceledOnly;

    public container pack()
    {
        return [#CurrentVersion, #CurrentList];
    }

    public boolean unpack(container _packedClass)
    {
        Version version = conPeek(_packedClass, 1);

        switch(version)
        {
            // Support old packed data
            case 1:
                [
                    version,
                    retainDays,
                    dataAreaId
                ] = _packedClass;

                simulateOnly       = NoYes::No;
                deleteCanceledOnly = NoYes::No;

                break;

            // Current version
            case #CurrentVersion:
                [version, #CurrentList] = _packedClass;
                break;

            default:
                return false;
        }

        return true;
    }
}

Why This Example Matters

This is how real enterprise systems evolve.
Not by rewriting everything.
But by extending existing logic safely.
Because production environments already contain:
  • scheduled batch jobs
  • persisted packed containers
  • historical execution data
If your versioning is bad:
  • old jobs fail
  • packed data becomes invalid
  • recurring jobs stop working
  • support tickets start multiplying rapidly 😅

What Actually Gets Stored?

Suppose version 1 stores:
[1, 30, "USMF"]
Later version 2 stores:
[2, 30, "USMF", NoYes::Yes, NoYes::No]
AX serializes this container internally before storing it in framework tables.
Conceptually it may look like:
0xA102B34F....
Not human readable.
Not developer friendly.
But AX runtime understands it perfectly.

What Senior Developers Immediately Notice

In version 1:
[version, retainDays, dataAreaId]
In version 2:
[
    version,
    retainDays,
    dataAreaId,
    simulateOnly,
    deleteCanceledOnly
]
Notice something important?
We did NOT modify old unpack logic.
That’s critical.
Because old serialized containers may still exist in:
  • Batch framework tables
  • SysLastValue
  • Recurring batch configurations
And AX still needs to understand them.

The Production Rule Nobody Talks About

Never assume:
“Nobody is using old packed data.”
Someone always is.
Usually production.
Usually after deployment.
Usually on Friday evening around 6:30 PM

Old vs Proper Approach

Bad Practice ❌Good Practice ✅
No versioningAlways use versions
Random container orderKeep strict order
Ignoring old dataHandle older versions
Hardcoded assumptionsFuture-proof logic
Testing only manuallyTest batch restore


Debugging Tips From Real Projects

Tip 1: Debug the Container

Always inspect packed container during debugging.
You’ll instantly see:
  • missing values
  • wrong order
  • nulls
  • version mismatch

Tip 2: Keep Same Sequence

Pack and unpack order MUST match exactly.
This is not optional.

Tip 3: Never Remove Old Versions Quickly

Old batch records may still exist in database.
Your old unpack logic might still be needed months later.

Tip 4: Add Comments for Future Developers

Because future developer might also be you.
And future-you will absolutely forget why version 3 exists.

Funny But Painfully Real 😅

Nothing builds character faster than:
  • Debugging corrupted containers
  • Restoring wrong packed data
  • Explaining to users why batch ran with last month’s filters

Final Thoughts

Remember These Rules

  • `pack()` saves state
  • `unpack()` restores state
  • serialization converts data into storable format
  • deserialization restores it back
  • Always use versioning
  • Never change container order carelessly
  • Think about future upgrades
  • Batch framework heavily depends on this
A good developer writes code that works.
A great developer writes code that still works after 3 upgrades, 2 customizations, and one panicked production deployment.

Comments