8.2 Complex expressions & flow control with error handling

Key Takeaways

  • Cloud flows run on the Workflow Definition Language (WDL), whose functions span string, collection, logical, conversion, date/time, and referencing categories.
  • Compose stores a single computed value for reuse, variables support reassignment across a flow, and Parse JSON turns a raw payload into strongly typed dynamic content using a schema.
  • Apply to each runs sequentially by default but supports Concurrency Control for parallel execution up to 50 threads.
  • A Try/Catch/Finally pattern is built from Scope actions whose Configure run after settings determine whether Catch or Finally executes.
  • The Terminate action explicitly sets a flow run's status to Failed, Cancelled, or Succeeded so a handled error isn't misreported as a plain success.
Last updated: July 2026

Cloud flows are built on the Workflow Definition Language (WDL), the JSON-based expression language underneath every visual designer canvas. Every value a flow computes dynamically — a concatenated string, a date offset, a value pulled from a prior step's output — is a WDL expression, and PL-400 candidates are expected to read and write them directly, not just click through the dynamic content picker.

Expression Categories

WDL functions fall into a handful of families:

CategoryExample functionsTypical use
Stringconcat(), replace(), split(), substring(), toUpper()Building composite text, parsing delimited values
Collectionfirst(), last(), length(), union(), intersect()Working with arrays from List rows or Parse JSON
Logicalequals(), and(), or(), not(), if()Conditional expressions written inline in a field
Conversionstring(), int(), float(), json(), xml()Casting between types, e.g. before math operations
Date/timeutcNow(), addDays(), formatDateTime()Scheduling logic and date-column comparisons
ReferencingtriggerBody(), triggerOutputs(), body('Action_name'), outputs('Action_name'), variables(), parameters()Pulling values from the trigger, a named action, a flow variable, or a flow parameter

Every expression starts with an @ prefix when embedded in raw JSON, though the visual designer hides that prefix inside its formula bar.

Compose, Variables, and Parse JSON

Three actions do a disproportionate amount of the work in complex flows:

  • Compose stores a computed value (a concatenated string, an if() result, a reshaped object) once so it can be referenced by name from later steps, instead of recomputing or re-copying the same expression repeatedly. It is a single assignment — cheaper than a variable when the value never needs to change.
  • Initialize variable / Set variable creates a named, mutable value that can be reassigned multiple times across the flow — the right choice for an accumulator inside a loop, where Compose's single-assignment model does not fit.
  • Parse JSON takes a raw JSON payload — typically an HTTP response or an untyped Compose output — and, using a schema (hand-written or generated from a sample payload), turns its properties into strongly typed dynamic content that appears by name in the picker for every later step, instead of forcing manual body('HTTP')?['field'] expressions.

Looping and Branching

  • Apply to each iterates an array (for example, the output of List rows). By default iterations run sequentially; Concurrency Control in the action's settings can be enabled to run iterations in parallel (up to 50 threads), trading strict ordering for throughput.
  • Condition (If) and Switch branch the flow based on an expression or a matched value, each branch containing its own set of actions.
  • Do until repeats a block until an expression evaluates true; it defaults to a cap of 60 iterations or a one-hour timeout, whichever comes first, both adjustable in the action's settings — a required safeguard against a runaway loop.

Error Handling with Scopes and Configure Run After

Cloud flows have no native try/catch keyword, so the standard pattern uses Scope containers combined with each action's Configure run after setting, which controls whether an action runs based on the outcome of the step(s) before it:

Run-after optionMeaning
is successfulRuns only if the prior action succeeded (the default)
has failedRuns only if the prior action failed
is skippedRuns only if the prior action was skipped (e.g., its own run-after condition wasn't met)
has timed outRuns only if the prior action exceeded its timeout

A Try/Catch/Finally pattern groups the primary logic in a Try scope, adds a Catch scope configured to run after Try has failed, and adds a Finally scope with all four run-after boxes checked so it always executes regardless of outcome — mirroring cleanup code in traditional programming.

Terminate

Inside a Catch scope, the Terminate action explicitly ends the flow run with a chosen status — Failed, Cancelled, or Succeeded — plus an optional custom error code and message. Without it, a flow whose Catch scope handles an error gracefully can be misreported in run history as "Succeeded," hiding a real failure from monitoring; Terminate lets the developer set the status that actually reflects what happened.

A Practical Error-Handling Pattern

A production-grade Catch scope rarely calls Terminate alone. A typical sequence inside Catch is: (1) a Compose action that pulls the failed action's error details out of the Try scope's outputs using an expression such as result('Try_Scope'), which returns the status and error of every action inside that scope; (2) a notification action (an email or a Teams message to an operations channel) that surfaces the composed error details to a human; and (3) a Terminate action that ends the run as Failed with a message summarizing what went wrong. This turns an otherwise silent failure into an actionable alert instead of a run that quietly disappears into history.

Nested Scopes

Scopes can be nested inside one another, which is useful for isolating error handling around a specific sub-process without wrapping the entire flow in one enormous Try block. For example, a flow that processes several unrelated integrations can give each integration its own Try/Catch pair, so a failure in one integration is contained and reported without stopping the others — a design consideration the exam frames as "flow control actions including error handling" rather than a single global try/catch.

Test Your Knowledge

In a Try/Catch/Finally pattern built with Scope actions, how should the Catch scope's Configure run after setting be configured?

A
B
C
D
Test Your Knowledge

A Do Until loop has a logic error and its exit condition is never satisfied. What stops the flow from running indefinitely?

A
B
C
D