Breaking Lines Badly

Line breaks can be confusing in AL. Here’s a little primer on how to work with Line Breaks in AL. Check out the video:

https://youtu.be/CqPzYI_PD0A

Line breaks seem simple enough, but they come with a surprising amount of history and platform-specific quirks. In this video, Erik explores how line breaks work in AL and Business Central — from the built-in backslash shortcut to working with character codes, and the important gotchas you’ll encounter when building files or splitting text.

A Brief History of Line Breaks

To understand why line breaks are complicated, we need to go back to the era of matrix printers and typewriters. There were two distinct mechanical operations involved in moving to a new line:

  • Line Feed (LF) — moves the paper up one line
  • Carriage Return (CR) — moves the print head back to the first character position

These operations were independent. On a printer, you could do a carriage return without a line feed and print on top of the same line for fancy overprinting effects. When monitors and terminals were introduced, the concepts from printers carried over to screens — but different systems adopted different conventions:

  • Unix/Linux — Line Feed only (character 10, \n)
  • Windows — Carriage Return + Line Feed (characters 13 then 10, \r\n)
  • Old Mac/Commodore — Carriage Return only (character 13)

This is why, if you open a Unix-originated text file in Notepad on Windows, you sometimes see no line breaks at all — Windows expects both characters in the correct order.

The Built-in Backslash Line Break in AL

Business Central has a long-standing convention: in a Message string, a backslash (\) is interpreted as a line break. So this works perfectly:

Message('Hello\World');

This displays “Hello” on one line and “World” on the next. Simple enough — but here’s where people get tripped up.

Why the Backslash Doesn’t Always Work

The backslash is interpreted when the message string itself is processed, not after parameter substitution. So if you pass the backslash as a parameter, it won’t be treated as a line break:

// This does NOT produce a line break
Message('%1', '\');

Similarly, storing the backslash in a variable and substituting it won’t work either. The substitution happens after the line break interpretation phase, so by the time the backslash arrives, it’s just a literal character.

Using Character Codes for Line Breaks

When you need line breaks in variables or dynamically constructed strings, you need to work with the actual character codes. In AL, you can assign specific characters by index into a Text variable:

var
    NL: Text[1];
begin
    NL[1] := 10;  // Line Feed character
    Message('Hello%1World', NL);  // This works!
end;

Erik tested the different combinations and found that Business Central effectively ignores character 13 (Carriage Return) and responds to character 10 (Line Feed) for display purposes. So for message output within BC, you only need the Line Feed character.

However, when you’re building files for external systems, the receiver’s platform matters:

  • If the receiver is a Windows-based machine, append CR+LF (13 then 10)
  • If the receiver is a Unix-based system, just use LF (10)

Building and Splitting Multi-Line Text

The source code demonstrates a practical example of working with line breaks in a CSV-like scenario. Here’s the complete implementation:

pageextension 50100 CustomerListExt extends "Customer List"
{
    trigger OnOpenPage();
    var
        CR: Text[1];
        NL: Text[1];
        csv: Text;
        Lines: List of [Text];
        r: Codeunit "Rest Client";
    begin
        CR[1] := 13;
        NL[1] := 10;
        csv :=
@'1000,Hello,123
2000,More,234
3000,Last,345
';
        Lines := csv.Split(NL);
        Message('Line 2 = %1', Lines.Get(2).TrimEnd(CR) + '!!!');
    end;
}

Let’s break down what’s happening here:

  1. Character setup: CR[1] := 13 and NL[1] := 10 create single-character text variables for Carriage Return and Line Feed respectively.
  2. Raw string literal: The @'' syntax creates a raw string with embedded line breaks as they exist in the source file.
  3. Splitting: csv.Split(NL) breaks the text into a list wherever a Line Feed character appears.
  4. Trimming: .TrimEnd(CR) is used to clean up any residual Carriage Return characters that might be hanging around if the source text used Windows-style CR+LF line endings.

Watch Out for Trailing Separators

Erik highlights an important gotcha with the Split function. If your text ends with a line feed character, the split will produce an extra empty element at the end of the list. For example, if you have:

total := line1 + NL + line2 + NL;
Lines := total.Split(NL);
// Lines now has 3 elements, not 2!
// Lines.Get(3) is an empty string

The trailing separator creates a blank entry. This is something to be aware of when iterating through split results — you may need to check for and skip empty entries.

Real-World Application

Erik mentions that in his Simple Object Designer app, where he’s programmatically constructing AL code, he uses a global line separator variable that gets appended throughout the code generation process. When you’re building files that need to produce well-formatted output — whether it’s code, CSV files, or any structured text — having a consistent approach to line breaks is essential.

Summary

Here’s what you need to remember about line breaks in AL and Business Central:

  • The backslash (\) works as a line break in direct Message strings, but not when passed through parameter substitution.
  • For dynamic line breaks, use character code 10 (Line Feed): NL[1] := 10.
  • Business Central treats character 10 as the effective line break; character 13 alone does nothing for display.
  • When building files for external systems, consider whether the target expects LF (Unix) or CR+LF (Windows).
  • When splitting text on a line feed, use TrimEnd(CR) to clean up any remaining carriage return characters.
  • Be aware that Split creates an extra empty element when the text ends with the separator.