GenericPackager
After writing several Java packager classes and noticing they were all the same boilerplate — an array of ISOFieldPackager objects, one per field — jPOS developer Eoin Flood wrote GenericPackager: a packager that reads field descriptions from an XML file and instantiates the right field packagers at startup.
Because packagers are created once at startup and shared across all messages, there is no runtime performance difference between a hand-coded Java packager and one driven by GenericPackager. The XML parsing happens only during initialisation.
XML format
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE isopackager PUBLIC
"-//jPOS/jPOS Generic Packager DTD 1.0//EN"
"http://jpos.org/dtd/generic-packager-1.0.dtd">
<isopackager>
<isofield
id="0"
length="4"
name="Message Type Indicator"
class="org.jpos.iso.IFA_NUMERIC"/>
<isofield
id="1"
length="16"
name="Bitmap"
class="org.jpos.iso.IFA_BITMAP"/>
<isofield
id="2"
length="19"
name="Primary Account Number"
class="org.jpos.iso.IFA_LLNUM"/>
<isofield
id="3"
length="6"
name="Processing Code"
class="org.jpos.iso.IFA_NUMERIC"/>
<isofield
id="4"
length="12"
name="Amount, Transaction"
class="org.jpos.iso.IFA_NUMERIC"/>
...
<isofield
id="128"
length="8"
name="Message authentication code"
class="org.jpos.iso.IFA_BINARY"/>
</isopackager>
<isofield> attributes
| Attribute | Required | Description |
|---|---|---|
id | Yes | Field number (0–192) |
length | Yes | Max field length; in chars for IFA_*, in bytes for IFB_BINARY, in hex-chars for IFA_BITMAP |
name | Yes | Human-readable description (used in log output) |
class | Yes | Fully qualified ISOFieldPackager class name |
pad | No | Maps to ISOFieldPackager.setPad(true); for IFB_NUMERIC this switches to LEFT_PADDED BCD |
trim | No | Maps to ISOFieldPackager.setTrim(true); suppresses padding on unpack |
token | No | Custom token attribute for some specialised packagers |
<isopackager> attributes
| Attribute | Default | Description |
|---|---|---|
maxValidField | 128 | Highest field number accepted |
bitmapField | 1 | Field number holding the bitmap |
emitBitmap | true | Whether to write the bitmap when packing |
thirdBitmapField | (none) | Field number that holds the tertiary (192-bit) bitmap inside a data element |
firstField | (auto) | Override the first field to pack |
Built-in packagers
jPOS ships several ready-to-use configurations:
On the classpath (loaded by class name)
| Resource | Description |
|---|---|
packager/iso93ascii.xml | ISO 8583:1993, full ASCII encoding |
packager/iso93binary.xml | ISO 8583:1993, BCD numerics, binary bitmap |
packager/iso87ascii.xml | ISO 8583:1987, ASCII |
packager/iso87binary.xml | ISO 8583:1987, binary |
packager/cmf.xml | jPOS Card Management Framework |
packager/cmf-858.xml | CMF variant with ISO 8858 extensions |
Load from classpath:
ISOPackager p = new GenericPackager("packager/iso93ascii.xml");
External files (in cfg/packager/)
| File | Description |
|---|---|
iso87ascii-binary-bitmap.xml | ISO 8583:1987, ASCII fields with binary bitmap |
iso2003binary.xml | ISO 8583:2003 binary |
vapvip.xml | Visa/VIP variant |
nyceiso8583.xml | NYCE network variant |
Load from filesystem:
ISOPackager p = new GenericPackager("cfg/packager/vapvip.xml");
Hand-coded Java alternative
For the rare case where XML isn't expressive enough, you can extend ISOBasePackager directly:
public class ISO93BPackager extends ISOBasePackager {
private static final boolean pad = false;
protected ISOFieldPackager fld[] = {
/*000*/ new IFB_NUMERIC ( 4, "Message Type Indicator", pad),
/*001*/ new IFB_BITMAP (16, "Bitmap"),
/*002*/ new IFB_LLNUM (19, "Primary Account number", pad),
/*003*/ new IFB_NUMERIC ( 6, "Processing Code", pad),
/*004*/ new IFB_NUMERIC (12, "Amount, Transaction", pad),
/*005*/ new IFB_NUMERIC (12, "Amount, Reconciliation", pad),
// ...
/*128*/ new IFB_BINARY ( 8, "Message authentication code")
};
public ISO93BPackager() {
super();
setFieldPackager(fld);
}
}
The fld array is indexed by field number. fld[0] = MTI packager, fld[1] = bitmap packager, fld[2] = field 2 packager, and so on. Gaps (fields defined in the spec but not used in your exchange) can be null or a placeholder.
Wiring a packager to a channel
In a QServer or ChannelAdaptor descriptor:
<channel class="org.jpos.iso.channel.NACChannel"
packager="org.jpos.iso.packager.GenericPackager"
logger="Q2">
<property name="packager-config" value="cfg/packager/myformat.xml"/>
<property name="host" value="..."/>
<property name="port" value="..."/>
</channel>
Or for the XML tutorial packager:
<channel class="org.jpos.iso.channel.XMLChannel" logger="Q2">
<property name="packager" value="org.jpos.iso.packager.XMLPackager"/>
</channel>
Composite fields with <isofieldpackager>
When a field contains its own sub-fields (e.g. field 48 with proprietary sub-elements, or field 55 with EMV TLV data), use <isofieldpackager>:
<isofieldpackager
id="48"
name="Additional Data — Private"
length="999"
class="org.jpos.iso.IFA_LLLCHAR"
packager="org.jpos.iso.packager.GenericSubFieldPackager">
<isofield id="1" length="2" name="Sub-element 1" class="org.jpos.iso.IFA_CHAR"/>
<isofield id="2" length="6" name="Sub-element 2" class="org.jpos.iso.IFA_LLCHAR"/>
<isofield id="3" length="10" name="Sub-element 3" class="org.jpos.iso.IFA_NUMERIC"/>
</isofieldpackager>
What this does:
- The outer packager allocates an
ISOMsgleaf (field 48) viaISOMsgFieldPackager.createComponent(). - The
IFA_LLLCHARouter packager reads/writes the length envelope (3 ASCII digits). - The inner
GenericSubFieldPackagerprocesses the payload bytes using the nested<isofield>definitions.
In application code:
ISOMsg de48 = (ISOMsg) msg.getValue(48);
String sub1 = de48.getString(1);
String sub2 = de48.getString(2);
Sub-fields appear as a child ISOMsg nested inside the parent, with their own field numbering starting at 1.
Comparing ASCII vs binary packager XML
The same field definition in both variants side by side:
<!-- ASCII (iso93ascii.xml) -->
<isofield id="0" length="4" name="MTI" class="org.jpos.iso.IFA_NUMERIC"/>
<isofield id="1" length="16" name="Bitmap" class="org.jpos.iso.IFA_BITMAP"/>
<isofield id="2" length="19" name="PAN" class="org.jpos.iso.IFA_LLNUM"/>
<isofield id="4" length="12" name="Amount" class="org.jpos.iso.IFA_NUMERIC"/>
<isofield id="35" length="37" name="Track2" class="org.jpos.iso.IFA_LLCHAR"/>
<!-- Binary (iso93binary.xml) -->
<isofield id="0" length="4" name="MTI" class="org.jpos.iso.IFB_NUMERIC" pad="false"/>
<isofield id="1" length="16" name="Bitmap" class="org.jpos.iso.IFB_BITMAP"/>
<isofield id="2" length="19" name="PAN" class="org.jpos.iso.IFB_LLNUM" pad="false"/>
<isofield id="4" length="12" name="Amount" class="org.jpos.iso.IFB_NUMERIC" pad="false"/>
<isofield id="35" length="37" name="Track2" class="org.jpos.iso.IFB_LLCHAR"/>
For field 2 (a 16-digit PAN):
| Variant | Wire bytes | Description |
|---|---|---|
| ASCII | 1641111111111111110 | "16" (2 ASCII digits) + "4111111111111111" (16 ASCII chars) |
| Binary | 0x16 0x41 0x11 0x11 0x11 0x11 0x11 0x11 0x11 | 0x16 = 22 decimal = 16 BCD digits; then 8 bytes of BCD |
Wait — 0x16 decoded as BCD is the digit pair 1,6 = decimal 16. The BCD length prefix encodes the number of decimal digits, not bytes.
For field 4 (amount "000000001000", 12 digits):
| Variant | Wire bytes | Wire size |
|---|---|---|
| ASCII | 000000001000 | 12 bytes |
| Binary | 0x00 0x00 0x00 0x10 0x00 0x00 | 6 bytes (12 digits / 2) |
Binary encoding halves the wire size for numeric fields — significant when multiplied across millions of transactions per day.