Display files (WORKSTN) are RPG's mechanism for interactive 5250 screen I/O. This chapter covers declaring and compiling display files, the runtime behavior of screen input and output, and the screen handlers that render the 5250 interface at runtime.
Display Files (WORKSTN)
Programs that declare a WORKSTN file require the corresponding DDS display file source (.dspf) to be available at compile time. Both free-format (DCL-F ... WORKSTN) and fixed-format F-spec declarations are supported:
FMYSCREEN CF E WORKSTN
Provide the directory containing these files with --dds-path:
rpgc --dds-path qddssrc source.rpgle
The compiler parses the DDS source to determine record formats, field definitions, and indicator conditioning. The .dspf files must be present locally - they cannot be resolved from a remote system.
If a display file fails DDS validation - for example a subfile control record that overlaps its subfile record (CPD7812) - each error is reported as a normal [ERROR] diagnostic located in the .dspf (file:line:col), and the compile fails with a non-zero exit status. Fix the display file source and recompile.
Keyboard Shift and Lowercase Input
A character input field with the default keyboard shift (alphanumeric shift - no data-type/shift entry in DDS position 35, or an explicit A) folds typed lowercase a - z to uppercase before the program receives the field, matching IBM i. For example, typing test into such a field delivers TEST. The fold happens at data entry, so the field also echoes uppercase on screen as the operator types, not only after submit.
To preserve lowercase, specify CHECK(LC) on the field, or CHGINPDFT(LC) at the record or file level (which applies to every character input field in that scope):
A XNAME 20A I 5 2CHECK(LC)
The fold applies only to input-capable character fields (usage I or B); numeric and output-only fields are unaffected.
Numeric Input Field Validation
Each numeric input field is validated before the format is accepted. If a numeric field contains data that is not a valid number, the input is rejected: the message line shows Value entered is not valid for a numeric field., the cursor moves to the field in error, and the program keeps waiting for a corrected value - matching IBM i, which refuses the entry at the keyboard (operator error 0024). The invalid content is never accepted as zero. A blank numeric field is valid and reads as zero.
Which keys run that check follows the DDS keyword that enabled the key:
| Key | Validates numeric input? |
|---|---|
| Enter | Yes |
Roll Up / Roll Down (ROLLUP, ROLLDOWN, PAGEDOWN, PAGEUP) |
Yes |
A function key enabled by CFnn (command function) |
Yes |
A function key enabled by CAnn (command attention) |
No |
A CFnn key transmits the modified screen data back to the program, so its input has to be valid first - it is refused and re-prompted exactly as Enter is. A CAnn key returns control without reading the field data, so it is accepted whatever the fields contain. This is why the conventional CA03(03 'Exit') still leaves a screen the operator abandoned half-typed, while the same key declared CF03 would not.
What a CAnn Key Returns in the Input Fields
The same distinction governs the data, not just the validation. A CAnn key transmits no input at all, so after it the record's input fields hold the values the program last wrote, not what the operator typed. Only the response indicator changes. A CFnn key, Enter, and the roll keys all return the typed values.
// AMOUNT held 0 when the format was written; the operator types 10.
exfmt SCR01;
// After CA03 : AMOUNT is 0 - the typed 10 was never transmitted
// After CF05 : AMOUNT is 10
This matters for the common "did the operator change anything?" guard on an exit path: under CAnn the field still reads as it was written, so the guard sees no change. Subfile row input behaves the same way - an option typed into a subfile record is discarded when a CAnn key ends the read.
INFDS - File Information Data Structure
A display file can declare an INFDS (File Information Data Structure) to capture runtime feedback from the system. The most common use is reading the function key AID byte at position 369 to determine which key the user pressed:
**FREE
DCL-F MYSCREEN WORKSTN InfDS(fileinfo);
DCL-DS fileinfo;
FunKey Char(1) Pos(369);
End-DS;
EXFMT SCREEN01;
If FunKey = x'3C'; // F12
// handle Back
EndIf;
After each EXFMT, the compiler automatically populates the INFDS subfield at position 369 with the 5250 AID byte corresponding to the pressed key. Common AID byte values:
| Key | Hex | Key | Hex |
|---|---|---|---|
| Enter | x'F1' |
F1 | x'31' |
| F3 | x'33' |
F5 | x'35' |
| F12 | x'3C' |
Roll Up (Page Down) | x'F5' |
| F24 | x'BC' |
Roll Down (Page Up) | x'F4' |
Note that the roll keys run opposite to the names on a PC keyboard: the key labelled Page Down is the Roll Up key (x'F5'), and Page Up is Roll Down (x'F4').
The record format name at positions 261 - 270 is also populated - with the name of the format the operation processed, blank-padded to the subfield width:
DCL-DS fileinfo;
FmtName Char(10) Pos(261); // e.g. 'SCREEN01' after EXFMT SCREEN01
FunKey Char(1) Pos(369);
End-DS;
The cursor position at positions 370 - 371 is also populated: byte 370 holds the 1-based screen row and byte 371 the 1-based screen column where the operator left the cursor when the key was pressed. Declare each byte as a small numeric subfield:
DCL-DS fileinfo;
FmtName Char(10) Pos(261);
FunKey Char(1) Pos(369);
CurRow Uns(3) Pos(370); // cursor row (1-based)
CurCol Uns(3) Pos(371); // cursor column (1-based)
End-DS;
The AID byte, the format name, and the cursor position are all populated after EXFMT (simple and subfile-control EXFMT SFLCTL) and after a display-file READ.
For a subfile, three further feedback subfields are populated, each a 2-byte integer:
DCL-DS fileinfo;
SflRrn INT(5) Pos(376); // subfile RRN just processed
MinRrn INT(5) Pos(378); // lowest subfile RRN displayed
SflCnt INT(5) Pos(380); // subfile record count
End-DS;
- Position 376 - the subfile relative record number just processed: set to the RRN by a
WRITEto the subfile, and to the RRN of the record just read byREADC. - Position 378 - the lowest subfile RRN currently displayed, set when the subfile is shown by
EXFMT SFLCTL. - Position 380 - the count of active subfile records. It is updated as records are written, and also by a
WRITEto the subfile-control format, so a program can read it straight after thatWRITEwithout displaying anything. Records initialized bySFLINZunderSFLRNAare not active and are not counted.
Roll Keys - ROLLUP, ROLLDOWN, PAGEDOWN, PAGEUP
A record format that declares a roll keyword receives the roll key like any other enabled key: the response indicator comes on, the INFDS AID byte reports the key, and any input the operator typed is returned with it.
A R SFLCTL SFLCTL(SFLREC)
A ROLLUP(50)
A ROLLDOWN(51)
EXFMT SFLCTL;
If *IN50; // operator rolled up (pressed Page Down)
LoadNextPage();
ElseIf *IN51; // operator rolled down (pressed Page Up)
LoadPriorPage();
EndIf;
PAGEDOWN and PAGEUP are accepted as exact synonyms - with the directions reversed, matching DDS: PAGEDOWN is the same key as ROLLUP, and PAGEUP is the same key as ROLLDOWN.
Which roll keywords a format declares decides who handles a roll:
| Format | Behavior when the operator rolls |
|---|---|
| Declares the roll keyword | The program handles the roll: the response indicator is set and control returns to it. |
| Subfile control with no roll keyword | The runtime pages the subfile, the way the system rolls a subfile whose SFLSIZ exceeds its SFLPAG without involving the program. |
| Any other format | The key is not enabled: the message line shows Function key not allowed. and the format stays up. |
A format that declares a roll keyword is never also paged by the runtime - the program owns the key outright.
For disk files, the INFDS file feedback area (positions 1 - 21) is populated after every I/O operation (CHAIN, READ, READE, READP, READPE, SETLL, SETGT, WRITE, UPDATE, DELETE):
| Position | Field | Type | Description |
|---|---|---|---|
| 1 - 8 | File name | CHAR(8) | External file name, truncated/padded to 8 characters |
| 9 | Open indicator | CHAR(1) | '1' when the file is open, '0' when closed |
| 10 | EOF indicator | CHAR(1) | '1' at end-of-file, '0' otherwise |
| 11 - 15 | Status code | ZONED(5:0) | 0 = success, 12 = record not found, or an error status after a failed operation (e.g. 1211 file not open) |
| 16 - 21 | Last opcode | CHAR(6) | The last I/O opcode (e.g. CHAINR, READ, SETLL) |
A disk-file INFDS subfield at a position the compiler does not populate is rejected at compile time with TRN2149 rather than left at zeros.
A disk file may name a file error subroutine with INFSR(name). When any disk-file operation - SETLL, SETGT, CHAIN, READ, READE, READP, READPE, UPDATE, DELETE, WRITE, OPEN, CLOSE, FEOD - fails and the error is not handled by an error indicator, an (E) extender, or a surrounding MONITOR group that can handle it, control transfers to that subroutine. "That can handle it" is the key qualifier: a file op inside a MONITOR whose ON-ERROR matches the error is handled by the MONITOR and the INFSR does not run; but if no ON-ERROR in the enclosing MONITOR(s) matches the error, the INFSR still runs. Before the subroutine runs, the file feedback is set to the failing operation's status, so inside the INFSR both %STATUS(file) and the INFDS status subfield (positions 11 - 15) hold the actual error status (e.g. 1211 for an operation on a closed file). After the subroutine's ENDSR, execution resumes with the operation following the one that failed. (A cycle-step return point in ENDSR - *GETIN, *DETC, *DETL, *TOTC, *TOTL, *OFL - is rejected at compile time; use a blank return point or *CANCL.)
Any disk-file operation - SETLL, SETGT, CHAIN, READ, READE, READP, READPE, UPDATE, DELETE, WRITE, OPEN, CLOSE, FEOD - may carry an error indicator in the LO position (fixed-format cols 73 - 74). On an I/O error that indicator is set *ON, %STATUS(file) is set, execution continues with the next operation, and the file INFSR is not invoked - the same precedence as the (E) extender. On a successful operation the indicator is set *OFF. The operation's other resulting indicators keep their own meaning (for example the SETLL/READE EQ indicator in cols 75 - 76). An error indicator and the (E) extender are alternatives - use one or the other, not both.
Numeric Output Editing - EDTCDE and EDTWRD
Numeric output fields in a display file can carry an EDTCDE (edit code) or
EDTWRD (edit word) keyword to control how the value is formatted on screen.
The two keywords are mutually exclusive on a single field - specifying both is
a compile error.
A BALANCE 9 2O 5 5EDTCDE(J)
A TOTAL 9 2O 6 5EDTWRD(' , , . ')
Supported EDTCDE codes and their on-screen effect:
| Codes | Grouping commas | Zero value | Negative sign |
|---|---|---|---|
1 2 3 4 |
1 2 only |
1 3 print, 2 4 blank |
none (printed unsigned) |
A B C D |
A B only |
A C print, B D blank |
trailing CR |
J K L M |
J K only |
J L print, K M blank |
trailing - |
Z |
never | digits only | none |
For a 9,2 field holding -1212000.00, EDTCDE(J) renders 1,212,000.00-
and EDTCDE(A) renders 1,212,000.00CR; a positive value reserves the sign
position with blanks. EDTCDE(Z) zero-suppresses to the significant digits
with no decimal point or sign (12.34 renders 1234).
EDTCDE(X) applies no editing: the field's digits at full declared width,
leading zeros retained, no decimal point, with the sign carried as a zoned
overpunch on the units digit - identical to a field with no edit keyword. A
5,2 field holding -12.34 renders 0123M. EDTCDE(Y) is the date edit
code: it inserts / separators after the second and fourth digits (mm/dd/yy
for a 6-digit field, mm/dd/yyyy for 8) and suppresses the single leftmost
zero to a blank (070826 renders 7/08/26).
EDTWRD formats the value through its edit word: each blank in the word is a
digit position (leading zeros suppressed), and inserted characters such as
commas and the decimal point appear literally.
A numeric output field with no edit keyword is displayed as its zoned external image (raw digits, zero-padded, with the sign carried as a zoned overpunch on the units digit).
Date, Time, and Timestamp Fields
DDS date (L), time (T), and timestamp (Z) fields are displayed at the
width implied by their DATFMT or TIMFMT keyword and rendered in the
matching format:
| DDS type | DATFMT / TIMFMT | Screen width | Example |
|---|---|---|---|
L |
*ISO, *USA, *EUR, *JIS, *JOB |
10 | 03/15/2026 |
L |
*MDY, *DMY, *YMD |
8 | 03/15/26 |
L |
*JUL |
6 | 26/074 |
T |
any (*HMS, *ISO, …) |
8 | 14:30:45 |
Z |
(none) | 26 | 2026-03-15-14.30.45.000000 |
When no DATFMT is specified, the default is *ISO; when no TIMFMT is
specified, the default is *ISO. Input-capable date and time fields accept
operator entry in the field's declared format and parse it back into the
program variable.
EXTFILE and EXTMBR
EXTFILE overrides the physical file (database table) opened at runtime,
independent of the DCL-F / F-spec name. The F-spec name becomes a
program-internal alias; the actual I/O targets the file named by EXTFILE:
DCL-F MYALIAS DISK USAGE(*INPUT)
EXTDESC('APPLIB/CUSTMAST')
EXTFILE(*EXTDESC);
// I/O operations reference MYALIAS, but the file opened is CUSTMAST
READ MYALIAS;
Supported forms:
| Keyword | Effect |
|---|---|
EXTFILE('filename') |
Opens the named file at runtime (literal) |
EXTFILE(*EXTDESC) |
Opens the file named by EXTDESC |
EXTDESC('lib/file') |
Specifies the compile-time file for field layout resolution |
EXTMBR('member') |
Accepted syntactically; no JVM effect (SQL tables have no members) |
Both free-format (DCL-F ... EXTFILE(...)) and fixed-format F-spec keyword
areas are supported. When EXTFILE is set, the DDS resolution (--dds-path)
also looks up the override file name instead of the F-spec name.
PASS(*NOIND) - Suppress Indicator Passing
The PASS(*NOIND) keyword on a WORKSTN file declaration is accepted by the compiler:
**FREE
DCL-F MYSCREEN WORKSTN PASS(*NOIND);
On IBM i, PASS(*NOIND) suppresses automatic indicator passing for program-described WORKSTN files. For externally-described files (which the compiler requires), the keyword is accepted and has no runtime effect, so source that uses it compiles without modification.
MAXDEV - Maximum Acquired Devices
The MAXDEV keyword on a WORKSTN file declaration controls whether the file
operates in single-device or multiple-device mode:
**FREE
DCL-F MYSCREEN WORKSTN MAXDEV(*ONLY); // single-device (default)
DCL-F MYSCREEN WORKSTN MAXDEV(*FILE); // multi-device, count from file
MAXDEV(*ONLY) restricts the file to a single device, which is the default when
no MAXDEV keyword is specified. MAXDEV(*FILE) enables multiple-device mode,
where the file can acquire up to the number of devices defined in the display
file. Any of DEVID, SAVEIND, MAXDEV(*FILE), or SAVEDS on a WORKSTN file
declaration makes it a multiple-device file.
Both free-format (DCL-F ... WORKSTN MAXDEV(...)) and fixed-format F-spec
declarations are supported.
DEVID - Device Identification Field
The DEVID(fieldName) keyword on a WORKSTN file declaration names a field that
holds the device name. On input from a multiple-device file, the runtime sets
this field to the name of the device that supplied the record:
**FREE
DCL-F MYSCREEN WORKSTN MAXDEV(*FILE) DEVID(WSDEVID);
DCL-S WSDEVID CHAR(10);
Both free-format and fixed-format F-spec declarations are supported. Any of
DEVID, SAVEIND, MAXDEV(*FILE), or SAVEDS on a WORKSTN file declaration
makes it a multiple-device file.
SAVEDS - Save/Restore Data Structure per Device
The SAVEDS(dsName) keyword on a WORKSTN file declaration names a data structure
that is saved and restored for each device acquired to a multiple-device file:
**FREE
DCL-F MYSCREEN WORKSTN SAVEDS(MYDS);
DCL-DS MYDS;
DSFIELD1 CHAR(10);
DSFIELD2 CHAR(10);
END-DS;
On IBM i, before an input operation on a multiple-device WORKSTN file, the runtime saves the current data structure contents into a per-device save area. After the input completes, the data structure for the responding device is restored. This allows programs to maintain separate screen state for each acquired device.
Both free-format (DCL-F ... WORKSTN SAVEDS(dsName)) and fixed-format F-spec
declarations are supported. Compiled programs run against a single program
device, so there is no second device to save and restore for.
NEXT - Select the Next Program Device
The NEXT operation names the program device whose input the next read of a
multiple-device WORKSTN file should come from. Factor 1 is the program-device
value (a field, character literal, or named constant); the file operand names
the WORKSTN file:
**FREE
DCL-F MYSCREEN WORKSTN;
NEXT(E) 'DEV02' MYSCREEN; // next read targets program device DEV02
READ MYSCREEN;
The fixed-format C-spec form places the device in Factor 1 and the file in Factor 2:
C 'DEV02' NEXT MYSCREEN
The optional (E) extender (or an error indicator in the LO position of a
fixed-format C-spec) traps I/O errors via %ERROR.
Behavior in rpgc: compiled programs run against a single program device per
WORKSTN file. With one device, the next read already comes from that device, so
NEXT is accepted and executes as a no-op - it never changes which device is
read. The operands are still validated at compile time: the file operand must
name a declared WORKSTN file, and the device operand is type-checked like any
other value.
Screen Handlers
This section is about choosing the on-screen UI a compiled program presents through its own main(). To run a compiled program from your own Java code and read or drive its screens as structured data - without a terminal - see Calling Compiled Programs from Java instead.
Programs with display file I/O (WORKSTN) need a screen handler to render the 5250 screen and capture user input at runtime. The --screen option selects which handler the compiled program's main() uses:
rpgc --screen ltd.whitehorn.rpg.handler.TerminalScreenHandler source.rpgle # default
rpgc --screen ltd.whitehorn.rpg.handler.GuiScreenHandler source.rpgle # Swing GUI
| Handler | Description |
|---|---|
TerminalScreenHandler |
Renders in a TTY terminal using ANSI escape sequences (via JLine). Requires an interactive terminal - throws IllegalStateException if stdin is not a TTY. This is the default when --screen is not specified. |
GuiScreenHandler |
Renders in a Swing desktop window. Does not require a TTY, making it suitable for environments where a terminal is unavailable. Green-on-black monospace rendering matching the classic 5250 look. |
HeadlessScreenHandler |
No-UI handler driven at the raw character-grid level. This is a low-level embedding primitive; for reading or driving a program's screens from Java, prefer RpgHost + captures (see Calling Compiled Programs from Java). |
All handlers support: EXFMT, WRITE, READ on regular formats, subfile display with Page Up/Down pagination, windowed formats, DSPLY operator messages (notification and inquiry), function keys F1 - F24, and INFDS AID byte population.
Entering F13 - F24. As on a 5250 keyboard, the extended function keys are reached with Shift held down: Shift+F1 is F13, Shift+F2 is F14, and so on through Shift+F12 for F24. This applies to both the terminal and GUI handlers, so a format declaring CF24(24) responds to Shift+F12.
In a terminal, whether that keystroke arrives depends on your terminal program, since terminals differ in how they encode shifted function keys. The handler accepts every encoding in common use - modern xterm (CSI 1;2P and CSI 15;2~ forms), older X Consortium xterm (CSI 11;2~), XFree86 xterm (SS3 2P), and the VT220 codes that name F13 - F20 directly (CSI 25~ and up). A terminal program that sends none of these - or that intercepts Shift+Fn for its own use before the program sees it - cannot deliver F13 - F24; if a shifted key does not register, check your terminal's key bindings first, or use the GUI handler, which reads the keystroke directly and is unaffected. Modifier chords other than Shift (Ctrl+Fn, Alt+Fn) are deliberately not treated as F13 - F24, so they never fire the wrong response indicator.
For subfiles, the DDS SFLRCDNBR(CURSOR) keyword is honored on the initial display: the subfile-control field carrying the keyword holds a relative record number, and the cursor is positioned on the first input-capable field of that record (the page containing it is shown first). A value of 0, blank, or a record number not present leaves the cursor on the first input-capable field of the first displayed record.
The DDS SFLEND(*MORE) keyword is honored: while its conditioning indicator is on, the subfile shows More... on the line below the display area when the current page is not the last, and Bottom on the last page. The indicator is right-justified to end at the last column. When the conditioning indicator is off, no indicator is shown. (The +-marker forms - plain SFLEND and SFLEND(*PLUS) - are not rendered.)
A program that declares no WORKSTN display file is compiled with no screen handler at all - the --screen option has no effect on it. Such a program (for example a DSPLY-only utility or a batch job) runs in any context without requiring a TTY: DSPLY messages go to standard output, as they do to the external message queue independently of any display file. Only programs that actually perform WORKSTN screen I/O wire a screen handler and are subject to the TTY requirement noted above for TerminalScreenHandler.
The --screen option is not available in --lib (library) mode, since library modules have no main() and therefore no screen handler lifecycle.
The --screen-title <text> option sets a custom window title for the GUI screen handler. The title is baked into the compiled class at compile time:
rpgc --screen ltd.whitehorn.rpg.handler.GuiScreenHandler --screen-title "Inventory Management" prog.rpgle
When omitted, the handler uses its default title ("rpgc - 5250").
Headless Bridge
The HeadlessBridge is a low-level tool for driving a program's raw 5250 screens over a stdio protocol - for a terminal-style front end, not for reading data into Java. If you are embedding a compiled program in Java code, prefer RpgHost + captures (Calling Compiled Programs from Java).
The HeadlessBridge class provides a JSON-lines stdio interface for driving compiled programs without a GUI or terminal. An external process spawns the bridge, sends commands over stdin, and reads responses from stdout:
java -cp triton-rpg.jar ltd.whitehorn.rpg.handler.HeadlessBridge ORDENTRY ./out
Commands are JSON objects with a method field: read_screen, send_key, send_keys, set_cursor, and status. The bridge loads the compiled class, wires a HeadlessScreenHandler, and runs the program on a background thread. Each response is a single JSON object terminated with a line feed (\n) - the terminator is always \n, never the host's platform line separator, so a client can split the stream on \n regardless of the operating system the bridge runs on. The same holds for all compiled-program text output (for example DSPLY messages): output bytes are identical on Linux, macOS, and Windows.
The read_screen and status responses report programRunning (false once the program stops) and programError. programError is null while the program is running or after it ends cleanly - a normal return or an orderly halt (a halt indicator or *INLR ending the cycle). If the program terminates abnormally on an unhandled exception (for example an unmonitored divide-by-zero), programError carries the exception's type and message, letting a client tell a crash from a clean end rather than seeing only programRunning go false.
For programs that use EXEC SQL or a disk file, the bridge bootstraps the JDBC connection the same way standalone main() does - reading the connection parameters from the environment variables the program was compiled to use. If you compiled with custom environment variable names, the bridge honors those names; set the corresponding variables in the bridge process's environment before launching.
Copyright © 2026 Whitehorn Ltd. Co. All rights reserved. Triton™ is a trademark of Whitehorn Ltd. Co.