A true DDS message subfile cannot show messages from an arbitrary shared *MSGQ such as QSYSOPR — only from a program message queue (a specific call-stack entry’s message queue). The QSYSOPR-style “shared rolling monitor screen” use case is addressed at the end with the correct, different mechanism for that job.
This article is for RPG and CL developers who already know ordinary data subfiles (SFLCTL/SFLPAG/SFLDSP) and basic message-queue mechanics, and now want the genuine DDS message-subfile pattern — SFLMSGRCD, SFLMSGKEY, and SFLPGMQ — along with an honest picture of its narrower scope.
Prerequisites
Before working through this article you should already be comfortable with:
- Ordinary DDS subfile mechanics:
SFLCTL,SFLPAG,SFLSIZ,SFLDSP,SFLDSPCTL,SFLCLR,SFLEND, and the record-format/control-format relationship. If any of that is unfamiliar, start with IBM i RPG Subfile Programming: SFLCTL, SFLPAG, SFLDSP and Paging — this article builds directly on that foundation and does not re-explain it. - Basic CL/RPG message-queue concepts:
SNDPGMMSG,RCVMSG, message types (*INFO,*ESCAPE,*NOTIFY, etc.), and the idea of a program message queue (tied to a call-stack entry) versus a named, standalone*MSGQobject. These are covered in IBM i Message Files and Exception Handling in CL. - Free-format RPGLE basics:
dcl-f,dcl-s,exfmt, and calling APIs from RPG.
Related but different: the message-files article linked above covers catching and sending exception messages in CL — SNDPGMMSG, MONMSG, RCVMSG, *ESCAPE/*NOTIFY propagation up the call stack. That is program-to-program error handling and has nothing to do with screen display. This article covers displaying the messages already sitting on a call stack’s program message queue, as a scrolling list inside a 5250 panel, using the dedicated message-subfile DDS keywords. The two topics share commands and vocabulary but solve different problems.
What a message subfile actually is
A message subfile is a special subfile record format, marked with the record-level keyword SFLMSGRCD, whose rows the display-file support fills directly from a program message queue — a message queue belonging to one specific entry on a job’s call stack — rather than from fields your program writes with ordinary WRITE operations.
This is narrower than “shows any message queue’s content” — a common misreading of the mechanism. Per IBM’s documentation, the field that identifies the source queue (SFLPGMQ) is explicitly documented as holding “the name of the program message queue used by the IBM i operating system to build a message subfile” — not an arbitrary *MSGQ object name. There is a related API, QMHSNDM (Send Nonprogram Message), for sending to a standalone *MSGQ, and IBM’s own documentation for QMHSNDPM points there explicitly for that case — which tells you plainly that QMHSNDPM (the API typically paired with program message queues) is not the tool for posting to QSYSOPR or a shop-created *MSGQ.
The three real keywords, and what each one is documented to do:
SFLMSGRCD(line-number)— a record-level keyword on the subfile record format. It marks the format as a message subfile and its parameter “specifies the first line on the display on which messages are displayed.” It does not take a subfile-record-format-name parameter; it takes a screen line number. How many messages display depends on the subfile’sSFLPAGvalue, same as any subfile.SFLMSGKEY— a field-level keyword for the first field in the message subfile’s record format. IBM’s documentation is explicit that this field is “predefined as a four-position, character data type, hidden field,” that it “must always be the first field defined in the subfile record format,” and that “the field name and SFLMSGKEY are the only DDS you can specify for this field.” Your program places a message reference key here to select which message a given subfile row represents.SFLPGMQ([10] | [276])— a field-level keyword for the second (and last) field in the same record format, immediately after theSFLMSGKEYfield. It holds the name of the program message queue the subfile draws from. The default length is 10 bytes; a 276-byte form exists for a fully qualified ILE call message queue name, with an optional module name in bytes 257–266 and an optional bound program name in bytes 267–276. The special value*means “the queue of the program issuing the output operation” — and that value explicitly cannot be used from a CL program.SFLPGMQcan also appear on the subfile-control record format, but only alongsideSFLINZ, and if specified on both the record and control formats the values must match. When it appears on the control record, IBM’s documentation and worked examples (e.g.A FLDPGM SFLPGMQ(276)) treat it exactly like the detail-record occurrence — a named field entry, not a bare keyword line.
There is no documented SFLMSGID, SFLMSGF, or SFLMSGQ keyword performing a row-population role. Those names do not appear in the actual message-subfile keyword set.
SFLMSG/SFLMSGID are a different thing entirely
SFLMSG/SFLMSGID are easy to confuse with row-level message display. They are not that. Per IBM’s documentation, SFLMSG and SFLMSGID are record-level keywords on the subfile-control record format, and their purpose is to “identify a message to be displayed on the message line” — the single error/status line shown when the user makes an entry error on that screen, the same role as ERRMSG/ERRMSGID but with the program (not the operating system) responsible for reverse-imaging fields and positioning the cursor. “Only one message can be displayed at one time,” and SFLDSP must be active for them to be processed. They have nothing to do with populating subfile rows from a queue — that role belongs entirely to SFLMSGRCD/SFLMSGKEY/SFLPGMQ.
SFLMODE is not a message/data toggle
SFLMODE is sometimes assumed to be a toggle between “message mode” and “ordinary data mode.” That is incorrect. SFLMODE is a record-level keyword on the subfile-control record format that returns the subfile’s folded-or-truncated display mode on input — it writes 0 (folded) or 1 (truncated) into a hidden one-character field you define, for use with SFLCSRRRN to locate the cursor within a subfile. It is an output-reporting keyword about display width behavior, not a mode switch your program sets to change what the subfile shows. There is no DDS keyword that toggles a subfile between message-queue content and ordinary program-written rows; a message subfile and a data subfile are structurally different record formats, and a screen that needs both uses two separate record-format/control-format pairs, shown or hidden as needed.
How messages get onto a program message queue
A program message queue belongs to a specific call-stack entry in a job — it is not a standalone object you create once with CRTMSGQ and have many independent jobs write into indefinitely. Messages land on it the normal way programs communicate up and down the call stack: SNDPGMMSG in CL, or the QMHSNDPM API in RPG, targeting a call message queue (or the job’s external message queue for interactive jobs).
IBM’s QMHSNDPM documentation confirms this scope directly: the API “sends … to a call message queue or the external message queue,” escape messages can go “only … with a message identifier and only to a call message queue,” and — critically for anyone pointing this API at QSYSOPR — “To send a message to a nonprogram message queue, see Send Nonprogram Message (QMHSNDM) API.” That is IBM telling you plainly that QMHSNDPM is the wrong tool for a shared, standalone operator queue.
For an immediate message (literal text, no message file), the message identifier parameter must be blank: “When sending a request or command message, you must use blanks,” and for the message-file parameter, “For an immediate message, use blanks for this parameter. If you specify a name anyway, the API ignores it and does not return an error.” The key parameter is populated by the API for every message type except status, where “no key is returned.”
DDS example: a correct message subfile against the caller’s own program message queue
A* Message subfile record format - IBM DOC CHECKED
A R MSGSFL SFL
A SFLMSGRCD(4)
A* First field: hidden 4-char message key (fixed position/type)
A MSGKEY SFLMSGKEY
A* Second field: program message queue (10-byte form)
A PGMQ SFLPGMQ(10)
A*
A* Subfile control format - IBM DOC CHECKED
A R MSGSFLCTL SFLCTL(MSGSFL)
A SFLSIZ(0024)
A SFLPAG(0010)
A 32 SFLDSP
A 31 SFLDSPCTL
A 30 SFLCLR
A 34 SFLINZ
A PGMQ SFLPGMQ(10)
A 33 SFLEND(*PLUS)
Notes on this fragment, each tied to what the IBM pages confirmed:
SFLMSGRCD(4)is a record-level keyword on the subfile (SFL) record format itself, not on the pairedSFLCTL(...)control record. IBM’s own worked example places it directly under theR SFLR ... SFLline, and the keyword’s documentation states plainly: “You use this record-level keyword on the subfile record format to specify that this subfile is to be a message subfile.” It still takes a line number (here, screen row 4), not a record-format-name parameter.MSGKEYis fixed at 4 characters, hidden, and must be the first field — per theSFLMSGKEYpage, “the field name and SFLMSGKEY are the only DDS you can specify for this field,” so the field line carries only the name and the keyword — no length, type, decimal, or usage attribute at all. IBM predefines it as a 4-character hidden field.PGMQis the second (and last) field in the record format, per theSFLPGMQpage’s stated position requirement;SFLPGMQ(10)is the documented default/explicit 10-byte form. Same rule asMSGKEY: name and keyword only, nothing else — IBM predefines this field as a 10-byte hidden field.- The control record’s
SFLPGMQ(10)is also a named field entry, reusing the field namePGMQ. IBM’sSFLPGMQreference and its own worked examples (e.g.A FLDPGM SFLPGMQ(276)) show the keyword attached to a named field wherever it appears, including on the control record alongsideSFLINZ. Reusing the exact same field name as the detail record (PGMQ) is deliberate: RPG’s default same-named-field behavior across record formats of one non-qualified externally-described display file keeps both occurrences in sync automatically — settingpgmqonce in the RPGLE updates the value both record formats see. SFLINZon the control record enables single-output mode, which theSFLPGMQdocumentation describes as loading “every message on that queue into the subfile in one operation.”SFLPGMQ“can also appear on the subfile-control record format, but only alongsideSFLINZ, and if specified on both the record and control formats the values must match” — so the control record also carries its ownSFLPGMQ(10), matching the detail record’s.SFLCLRandSFLENDboth require a conditioning option indicator — IBM’sSFLCLRpage: “An option indicator is required for this keyword”; IBM’sSFLENDpage: “An option indicator must be specified for this keyword.” Indicator 30 conditionsSFLCLRhere, matching the RPGLE example’sindDs.sflClrfield (pos(30)) below. Indicator 33 conditionsSFLEND(*PLUS), matching theindDs.sflEndfield (pos(33)).SFLDSPCTLandSFLDSPuse indicators 31 and 32 respectively, matching the RPGLE’sindDs.sflDspCtl(pos(31)) andindDs.sflDsp(pos(32)) fields. Conditioning both on the DDS side means the clear-onlywrite msgsflctl(indicators 31/32 off at that point) is a true clear-without-display, and the finalexfmt(31/32 turned on first) is the actual display pass — without that conditioning, the control record’s display/control image would always be active regardless of which write triggered it.
The three distinct control-record outputs below deliberately separate clear, initialize, and display: SFLCLR (indicator 30) and SFLINZ (indicator 34) are not active on the same WRITE. The displayed state uses 31 = SFLDSPCTL, 32 = SFLDSP. The end indicator (33) is on after the single initialization pass; it does not mean “more.” This is an offline, documentation-checked teaching pattern — not a claimed IBM i compile/runtime pass.
Free-format RPGLE example
**free
// STATUS: NOT TESTED — illustrates call shape only; not compiled or run on a live system
ctl-opt dftactgrp(*no) actgrp(*new);
dcl-f msgsfld workstn indds(indDs);
dcl-ds indDs qualified;
sflClr ind pos(30);
sflDspCtl ind pos(31);
sflDsp ind pos(32);
sflEnd ind pos(33);
sflInz ind pos(34);
end-ds;
dcl-pr QMHSNDPM extpgm('QMHSNDPM');
msgId char(7) const;
msgFile char(20) const;
msgData char(256) const;
msgDataLen int(10) const;
msgType char(10) const;
callStackEntry char(10) const;
callStackCounter int(10) const;
msgKey char(4);
errCode char(16);
end-pr;
dcl-s msgKey char(4);
dcl-s errCode char(16) inz(*allx'00');
dcl-s msgData char(256);
// ── Send an *INFO message onto THIS call level's own program message
// queue. Note msgId and msgFile are blank -- required for an
// immediate (literal-text) message per the QMHSNDPM documentation.
msgData = 'Status: 4,215 of 10,000 records processed.';
QMHSNDPM(
' ' : // msgId: blank, required for immediate msg
' ' : // msgFile: blank, required for immediate msg
msgData :
%len(%trim(msgData)) :
'*INFO ' :
'* ' : // callStackEntry: '*' = caller's own queue
0 :
msgKey :
errCode
);
// ── Initialize and display the message subfile, scoped to THIS job's
// own call-stack entry ('*' in PGMQ).
pgmq = '*'; // SFLPGMQ's runtime value must be set
// before the write that triggers SFLINZ's
// single-output load -- per IBM's SFLPGMQ
// documentation, SFLINZ loads "every message on
// that queue into the subfile in one operation"
// using whatever program message queue name is
// in this field at the time of that load. '*'
// matches the same call-stack scope already used
// above for QMHSNDPM's callStackEntry parameter.
// RPG right-pads a char field with blanks on
// assignment, filling PGMQ's 10-byte form.
// Phase 1: clear with display and initialization disabled.
indDs.sflDspCtl = *off;
indDs.sflDsp = *off;
indDs.sflInz = *off;
indDs.sflClr = *on;
write msgsflctl;
indDs.sflClr = *off;
// Phase 2: initialize from the selected program message queue.
// Avoid selecting SFLCLR and SFLINZ on the same output operation.
indDs.sflInz = *on;
write msgsflctl;
indDs.sflInz = *off;
// Phase 3: display the initialized message subfile.
indDs.sflDspCtl = *on; // DDS 31 SFLDSPCTL
indDs.sflDsp = *on; // DDS 32 SFLDSP
indDs.sflEnd = *on; // all queued messages loaded for this display
exfmt msgsflctl;
*inlr = *on;
return;
Points worth being explicit about:
msgIdandmsgFileare blank, matching the documented requirement for an immediate message: “For an immediate message, use blanks for this parameter.”callStackEntry = '*'targets the sending program’s own call message queue.pgmq = '*'is assigned immediately before the clearing/initializationWRITEsequence, deliberately matching that same'*'scope, soSFLPGMQ‘s runtime value is actually set before the initialization write that triggersSFLINZ‘s load. Per the documentation,*“cannot be used from a CL program” — only from a program, as here.- This mechanism cannot be redirected at
QSYSOPRor any other standalone*MSGQ— see the use-case section below for why, and for the correct alternative. indDs.sflClris on only for the first (clear-only)WRITE;indDs.sflInz(34) is on only for the second (initialize-only)WRITE. Both are off beforeEXFMT.indDs.sflDspCtl/sflDspturn the subfile and control record on for display, andindDs.sflEndis set*onalongside them, matching the DDS’s indicator-33 conditioning ofSFLEND(*PLUS)— this is a single-shotSFLINZload, so selecting the end indicator (ON) suppresses +More for this display cycle. If the application loads more messages later, its indicator must reflect that different paging state.indDs.sflDspCtl/sflDspalso matter for a second reason: they are the same indicators (31/32) that the DDS’sSFLDSPCTL/SFLDSPkeywords are conditioned on, so the clear/initialize writes (indicators still off at that point) and the finalexfmt(indicators turned on first) actually differ in what they do, rather than both being treated as full display passes.
Expected output and behavior
With SFLINZ in effect, the subfile control record load populates the subfile in a single operation from the program message queue identified by SFLPGMQ‘s runtime value, starting display at the screen line given to SFLMSGRCD. SFLMSGKEY identifies which specific message each row represents, which your program can read back for further action (e.g., removing a specific message) using the key value on input. Paging behaves like any subfile (SFLPAG/SFLEND). The display reflects the queue’s contents as of that load — it is not continuously live unless the program loops on clear/reload/EXFMT.
Common mistakes and troubleshooting
- Assuming
SFLPGMQcan name any*MSGQ. It cannot — it is documented strictly as a program message queue field. Pointing it atQSYSOPRor a shop-createdCRTMSGQobject is not a supported use of this keyword. - Treating
SFLMSGID/SFLMSGF/SFLMSGQas real keywords. They are not part of the documented message-subfile keyword set and will not compile as written. - Treating
SFLMSG/SFLMSGIDas row content. These keywords only control the single control-record message line, not subfile row population. - Treating
SFLMODEas a data/message toggle. It reports folded/truncated display state; it does not switch subfile content sources. - Putting the key and queue fields in the wrong order, or giving
SFLMSGKEYextra DDS. Both are documented as fixed:SFLMSGKEYmust be field one with no other DDS besides the keyword itself;SFLPGMQmust be field two and last. - Leaving the control record’s
SFLPGMQas a bare keyword line with no field name, or never assigning that field a value before the clearing write.SFLPGMQ(10)needs a named field attached on the control record too, and the RPGLE must setpgmqbefore the initialization write that triggersSFLINZ‘s load — otherwise the load runs against a stale or blank value.
Performance and security considerations
- Scope is already narrow by design. Because the source is a program message queue tied to a call stack entry, exposure is naturally limited to that job/call level — unlike a shared
*MSGQ, there is no cross-job authority surface to manage on the queue itself. - Immediate-message parameters must be blank. Passing a non-blank message file or ID for an immediate message is silently ignored by the API rather than erroring, per IBM’s documentation — a source of quiet logic bugs if your code assumes validation will catch it.
- SFLINZ single-output cost. Loading an entire queue’s messages in one operation is efficient for typical queue depths but should be understood when queue volume is high, to budget subfile load time.
Practical use case: a job’s own in-program message subfile, and the correct alternative to a QSYSOPR-style shared monitor
A message subfile, built correctly, is the right tool for showing the messages piling up on a program’s own call-stack entry — for example, a long-running interactive utility that wants to show the user the escape/diagnostic messages a called subroutine or service program has sent up the stack, without the developer re-implementing message retrieval and formatting by hand.
It is not the right tool, and per the documentation above cannot be made the right tool, for a different and very common goal: a QSYSOPR-style shared rolling display of operator messages pulled from an arbitrary nonprogram *MSGQ. SFLPGMQ is documented as a program message queue field, full stop, and QMHSNDPM‘s own documentation redirects anyone targeting a nonprogram queue to a different API (QMHSNDM) for the send side. There is no DDS message-subfile keyword combination that reads QSYSOPR or a shop-created standalone queue directly.
The correct design for that use case is a different mechanism: an ordinary (non-message) data subfile that your program populates itself, by retrieving entries from the target *MSGQ — via RCVMSG (CL) or the QMHRCVM/list-message APIs (RPG), without removing them — and WRITE-ing each retrieved message’s formatted text into a normal subfile record, the same way you would write any other row of program-supplied data. This is explicitly a different mechanism from a true DDS message subfile: there is no SFLMSGRCD/SFLMSGKEY/SFLPGMQ involved at all, no automatic row population by the display-file support, and your program is fully responsible for reading, formatting, and writing each row, just as with any ordinary subfile covered in IBM i RPG Subfile Programming: SFLCTL, SFLPAG, SFLDSP and Paging. Readers who came here specifically wanting the QSYSOPR-style shared screen should build that pattern — an ordinary subfile fed by API reads of the target queue — not the DDS message-subfile keywords documented above, which are scoped to a different, narrower job.
FAQ
Can SFLPGMQ point at QSYSOPR? No. IBM’s documentation describes the field strictly as holding the name of the program message queue used to build a message subfile; it is not documented as accepting an arbitrary *MSGQ object name.
What are SFLMSGID, SFLMSGF, and SFLMSGQ? They are not documented message-subfile keywords. The real keyword set is SFLMSGRCD (record-level), SFLMSGKEY (first field), and SFLPGMQ (second field).
What does SFLMSG/SFLMSGID actually control? The single error/status message line on the subfile-control record format — the same role ERRMSG/ERRMSGID play elsewhere, but with the program responsible for cursor positioning and reverse image. Not subfile row content.
What does SFLMODE actually report? Whether the subfile was in folded or truncated display mode on input (0 or 1 in a hidden field), for use with SFLCSRRRN. It is not a message/data content toggle.
How do I show QSYSOPR-style messages in a subfile, if not with a real message subfile? Use an ordinary data subfile and populate it yourself from RCVMSG/QMHRCVM reads of the target queue, writing each message as a plain subfile row. This is a different, program-driven mechanism, not the DDS message-subfile keywords.
Does QMHSNDPM support sending literal text without a message file? Yes, as an immediate message, but the message identifier and message file parameters must both be blank for that case, per the API documentation; a supplied message file name is silently ignored.
Why does the RPGLE set pgmq = '*' before the write? Because SFLPGMQ is a runtime field, not a compile-time constant — the subfile load that SFLINZ triggers reads whatever value is in that field at the moment of the write. If the program never assigns it, the load runs against a stale or blank value instead of the intended queue.
Sources Consulted
- SFLMSGRCD keyword — confirmed record-level keyword; parameter is a line number: “specifies the first line on the display on which messages are displayed.”
- SFLMSGRCD keyword (record-level placement) — confirmed it belongs on the subfile record format itself, not the control record: “You use this record-level keyword on the subfile record format to specify that this subfile is to be a message subfile.” Cross-checked against IBM’s own worked example at
ibm.com/docs/ssw_ibm_i_74/dds/msgsubexamp.htm. - SFLCLR keyword — confirmed “An option indicator is required for this keyword.”
- SFLEND keyword — confirmed “An option indicator must be specified for this keyword.”
- SFLMSGKEY keyword — confirmed first-field requirement: “This field is predefined as a four-position, character data type, hidden field” and “This field must always be the first field defined in the subfile record format.”
- SFLPGMQ keyword — confirmed scope and position: field “contains the name of the program message queue used by the IBM i operating system to build a message subfile”; must be the second and last field in the record format;
*value “can’t be used from a CL program”;SFLINZenables single-output load of “every message on that queue into the subfile in one operation.” The same page’s worked examples (e.g.A FLDPGM SFLPGMQ(276)) show the keyword attached to a named field wherever it appears, including on the control record. - SFLMSG keyword — confirmed control-record-level purpose: “identify a message to be displayed on the message line,” with “Only one message can be displayed at one time.”
- SFLMODE keyword — confirmed it “returns … an indication of whether the subfile was in folded or truncated mode on input,” values 0/1, used with
SFLCSRRRN. - QMHSNDPM (Send Program Message) API — confirmed it sends only “to a call message queue or the external message queue,” with “To send a message to a nonprogram message queue, see Send Nonprogram Message (QMHSNDM) API”; confirmed immediate-message parameters “must use blanks” for message identifier, and message file name is “ignored” if supplied.
- This site’s own IBM i RPG Subfile Programming: SFLCTL, SFLPAG, SFLDSP and Paging for the ordinary-subfile mechanics the QSYSOPR-style alternative in this article uses.
- This site’s own IBM i Message Files and Exception Handling in CL for the
SNDPGMMSG/RCVMSG/call-stack concepts referenced throughout, explicitly differentiated from this article’s screen-display focus.
Verification status
- DDS fragment: IBM DOC CHECKED. Keyword names, placement (
SFLMSGRCDon the subfile record format), field order and predefined attributes (SFLMSGKEYfirst,SFLPGMQsecond and last, name-and-keyword only), the namedSFLPGMQfield on the control record, and the option indicators required bySFLCLRandSFLENDall reflect the IBM i 7.4/7.5 DDS reference pages listed above. The fragment has not been compiled. - RPGLE example: NOT TESTED. It has not been compiled or run on any system; it illustrates parameter shapes consistent with the
QMHSNDPMdocumentation and should be verified against your release before use. SFLMSGID/SFLMSGF/SFLMSGQare not documented keywords and are not used anywhere in this article.- Offline validation only: the DDS and RPGLE above have been checked against IBM’s documentation and with offline column/structure checks — they have not been compiled on PUB400 or any live IBM i system.