Validate() – All Tables / All Fields / Always
At BC TechDays 2024 in Antwerp, Christian Hovenbitzer and I gave a session with a deliberately provocative title: Validate() – all tables / all fields / always. The room was packed, which tells you this is something people have strong opinions about.
You can watch the full recording on YouTube if you want to follow along.
The short version of our argument: calling Validate() on a field should be your default. Skipping it should require a conscious, documented decision — not habit.
What Validate() Actually Does
Before getting into the argument, it helps to be precise about what calling Validate() on a record field does.
It checks the table relation (if one exists and validation is not disabled), and it executes validate code in exactly five places — in this order:
What it does not check: DecimalPlaces, Min/MaxValue, CharAllowed, and a few other properties. Those only fire for user interface validation — when a user types into a field on a page. From code, they’re silently ignored.
📖 Docs: The full
Record.Validatemethod reference, including the optionalNewValueparameter, is on Microsoft Learn .
The Core Argument
Why should you always validate? Three reasons.
You always want valid data. That sounds obvious, but the interesting part is the second reason: you don’t actually know what “valid” means for a given field at runtime. In C/AL days you merged every module manually into one database. You knew exactly what code was in there. In AL, any extension can attach OnBeforeValidate or OnAfterValidate to any field — including Microsoft pushing new base app updates without you noticing. The definition of valid for a given field can change at any point in time.
Not calling validate potentially breaks other extensions. If an AppSource app registers an event subscriber on a field’s validate trigger to populate its own data, and your code just does := someValue instead, their logic never runs. That’s your bug, not theirs.
Pages always call validate — fortunately. The question Christian raised that started this whole session: if pages never let you skip validation, why should developers writing AL code be allowed to?
“But what if performance is bad?”
This was the most common objection we collected from the community. The slide I enjoyed showing here was this one:
Skipping checks to make things faster is not a performance optimization. It’s a time bomb.
For actual numbers: Christian wrote six test functions — three variants each for a real Sales Line quantity validate and an empty validate on a custom table.
An empty validate on a field with no code: 7 milliseconds for a single call. Even 1000 of them come in at 4ms. For a real validate with actual business logic (the Sales Line quantity validation is not trivial), you’re at 30ms per call — and if you genuinely need 100,000 of those, that’s a bulk scenario where you should rethink the architecture, not skip the triggers.
If the validate code is genuinely slow, that’s a bug. Report it. Don’t bury it with an assignment.
Chained (or Nested) Validates
The concern here is: if validating field A triggers a validate on field B which triggers field A again — you have a loop.
The answer is that if the code was designed that way, there’s probably a reason. And if the order matters, the table should expose a helper function. This is the pattern we showed:
If you have fields like UnitPrice, UnitPriceIncludingVat, LineAmount, and Discount where only two actually need to be called externally, put a function on the table that takes those two values and validates them in the right order. Your callers don’t need to know the internal dependency graph.
For the circular case — classic example: StartDate and EndDate validating each other — a clean pattern is to assign both fields first, then call Validate() on them without a new value. The validate triggers still fire and check the table relation, but since the assignment already happened, there’s no loop.
One caveat: if the validate code checks xRec to detect whether the value actually changed, this pattern won’t work — the validate won’t see a change because the assignment happened before the trigger fired. Write validate code that doesn’t depend on that where possible.
Temporary Tables
We split this into two cases.
Dedicated temporary tables (table type Temporary) — validate normally. The code on that table knows it’s running in a temporary scope, and it was designed for it. No special handling needed.
Hijacked temporary tables — a regular table used as a temporary record variable (classic example: Sales Line used as a temp buffer). This is where it gets dangerous, and Christian had a demo to illustrate why.
The problem: when you call DimensionManagement.ValidateShortcutDimValues (a base app codeunit), that function creates a new Dimension Set Entry in the database if one doesn’t exist yet. It doesn’t know it’s being called from a temporary scope. So you end up with real database records created as a side effect of working with a temp table — and that’s one of the gentler examples. Imagine what happens with something that writes sales documents.
Our recommendation: avoid hijacking regular tables as temp tables in the first place. Create a dedicated temporary table for your scenario. If you genuinely can’t avoid it, decide on a case-by-case basis whether to call validate, and check that the validate code handles IsTemporary() correctly.
Exceptions — When to Use Assignment Instead
There are legitimate cases to skip Validate():
Test code is the clearest one. In tests you want to control exactly which fields have which values to create a reproducible setup. Validating during test data preparation introduces unpredictability — third-party extensions might subscribe to the trigger and modify your test data under you. If you want to test validation code, call validate in the [When] step. If you’re just building the [Given], assign.
Data migration tables (tables that exist only to hold data during a migration before being destroyed) — technically fine to skip validate. They have no code, won’t get extended, and won’t be touched again after the migration.
Buggy validate code — if there’s a confirmed bug that makes the trigger unsafe to call, you can skip it with an assignment as a temporary workaround. Document it with a // TODO comment, report it, and remove the workaround when the fix ships.
TransferFields, ModifyAll, DataTransfer
These three all have the same characteristic: they don’t execute validate triggers.
TransferFields is mostly used for posting — copying from a document to a posted document. The argument “posted tables don’t have validate code” is less true every day; extensions do add it. It also doesn’t protect you against field ID mismatches.
ModifyAll skips all validate triggers. The workaround if OnModify code exists is just to loop and validate manually. Stefan mentioned wanting a third parameter on ModifyAll that would run validation triggers when code is present — that idea was out there as a feature request but hadn’t landed at the time of the session.
DataTransfer doesn’t call any triggers by design, and is only available in upgrade codeunits — so it’s at least limited in scope.
The Don’ts Slide
The full summary of what not to put in a validate trigger:
The Record.ChangeCompany one in there is half a joke, but only half. The CurrFieldNo and xRec points are serious — both work fine when called from a page (as they were designed for), but behave unexpectedly when called from code, and both are hard to test correctly.
LinterCop Rule
During Q&A, someone asked if I’m planning to add a LinterCop rule for this. The answer at the time was: yes, the idea is there. It would probably be an opt-in (disabled by default) rule that warns on := assignments to fields when validate should be called. I hadn’t worked out the pragma/suppression story for the exceptions yet.
Start Refactoring
If you have a codebase full of := assignments you want to convert, the AL Code Actions extension by David Feldhoff
can refactor them in bulk. Select the assignments, run the code action, done.
📖 Docs: LinterCop, the community linter for AL, is on GitHub . A future rule for enforcing
Validate()calls is something I’ve been thinking about — feel free to open an issue or vote for it there.
The deeper point is the mindset shift: validate is the default. Assignment is the exception that needs a reason.
This post was drafted by Claude Code from the session recording and slides from BC TechDays 2024. The full recording is on YouTube if you want to watch the demos and Q&A. (I did read and check the output before posting, obviously 😄)









