How long is a piece of string in AL?

The length of a string is a rather complicated concept in a world beyond 8-bit ASCII. This week I got hit by …. well, figuring out how long a string is, combined with some BC strangeness. Check out the video:

https://youtu.be/XLBEpMXDJJI

In this video, Erik takes us on a debugging adventure that started with a mysterious HTTP request failure in Business Central. What seemed like a simple web service call turned into an exploration of string encoding, UTF-8 byte lengths, and why the AL StrLen function might not give you the answer you actually need when working with HTTP content headers.

The Setup: A Simple HTTP POST

The story begins with a straightforward task: calling a web service from AL. Erik sets up the standard HTTP variables — HttpClient, HttpRequestMessage, HttpResponseMessage, HttpContent, and HttpHeaders — and builds a POST request:

// Build up the request
Request.SetRequestUri('https://microsoft.com');
Request.Method('POST');

// Create some data
Data := '💀💀💀'; // or '❤️❤️❤️'

// Package data into content
Content.WriteFrom(Data);

// Add content-length header
Content.GetHeaders(Headers);
Headers.Add('Content-Length', Format(StrLen(Data)));

// Attach content to request
Request.Content(Content);

// Send it
if Client.Send(Request, Response) then
    Message('Erik messed up')
else
    Message(GetLastErrorText());

The expectation is simple: build a request, set the content, add a Content-Length header, and send it off. But the result was an error: “There was an error while executing the HTTP request. Make sure you are connected to a valid endpoint.”

When Does HttpClient.Send Fail?

Erik had previously asked Microsoft about the failure conditions for HttpClient.Send, and they actually updated the documentation on learn.microsoft.com. The documented failure cases are:

  • The request URI is not an absolute URI
  • The chosen HTTP method is not supported
  • The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation, or timeout
  • The request failed due to a timeout

None of these seemed to apply — the URL was valid, POST is a supported method, and the failure was happening almost instantly, ruling out timeouts and network issues.

Narrowing Down the Problem

Through experimentation, Erik discovered that removing the Content-Length header made the request succeed. The header itself was the problem. But why?

Adding a message to display the calculated content length revealed the issue. When sending three emoji characters (skulls or hearts), StrLen returned 12 — but the actual data was only 6 visible characters. When switching to plain ASCII text like “Business Central rules”, StrLen returned 23, which was correct, and the request worked.

The Root Cause: Unicode vs. UTF-8

Here’s the key insight: Business Central works internally in Unicode (UTF-16). In UTF-16, most characters are represented in 16 bits, but some characters — like emoji — require more than 16 bits (they’re represented as surrogate pairs).

When you call Content.WriteFrom(), Business Central converts the string to UTF-8 behind the scenes before sending it over HTTP. The problem is that StrLen returns the length in the internal Unicode representation, not in UTF-8. And there’s no overload for StrLen that lets you specify a different encoding.

Through trial and error, Erik found that a single heart emoji (❤️) has a StrLen of 2 in Business Central, but its actual UTF-8 byte length is 6. Sending a Content-Length header with the wrong value causes the receiving server (or the HTTP framework itself) to reject the request.

The Solution: Calculate UTF-8 Length Using TempBlob

Since AL doesn’t provide a built-in way to get the UTF-8 byte length of a string, Erik devised a workaround using a TempBlob and an OutStream with explicit UTF-8 encoding:

procedure StringLengthUTF8(Text: Text): Integer
var
    TempBlob: Codeunit "Temp Blob";
    OutStream: OutStream;
begin
    TempBlob.CreateOutStream(OutStream, TextEncoding::UTF8);
    OutStream.WriteText(Text);
    exit(TempBlob.Length());
end;

The trick is elegant: by creating an OutStream with TextEncoding::UTF8, the text is converted to UTF-8 as it’s written into the blob. The blob’s length then gives you the actual byte count in UTF-8 encoding.

Using this function instead of StrLen for the Content-Length header solves the problem:

// Instead of:
Headers.Add('Content-Length', Format(StrLen(Data)));

// Use:
Headers.Add('Content-Length', Format(StringLengthUTF8(Data)));

With the emoji data, StrLen reported 2 while StringLengthUTF8 correctly returned 6 — and the HTTP request succeeded.

The Real-World Scenario

Erik’s actual production issue was subtler than emoji. He had made a change that altered the content being sent in a POST request. Previously, the content was already UTF-8 encoded (so StrLen happened to return the right byte count), but after the change, the content was in Business Central’s internal Unicode format. Business Central dutifully converted it to UTF-8 on the fly, but the Content-Length header was still being calculated with StrLen, producing an incorrect value and a confusing error message.

Wishlist for AL

Erik wraps up with a couple of suggestions for the AL language team:

  • An overload for StrLen that accepts a TextEncoding parameter, so you can get the length in UTF-8, ASCII, or other encodings directly
  • A .Length() method on string/text types — similar to the fluent methods that have been added to strings recently — ideally with an encoding overload
  • A better error message when the Content-Length header doesn’t match the actual content length, rather than the generic “make sure you are connected to a valid endpoint” message

Summary

The takeaway is clear: in Business Central, StrLen gives you the character count in the internal UTF-16 representation, not the byte count in UTF-8. When you’re working with HTTP and need to set a Content-Length header — or any time the byte-level size matters — you need to account for encoding differences. The TempBlob + OutStream with TextEncoding::UTF8 pattern is a reliable workaround until Microsoft provides a more direct solution in the AL language. And if your HTTP requests are mysteriously failing with vague error messages, check your Content-Length header — it might just be a string encoding issue hiding in plain sight.