FormulaEval.FormulaBuilder chain calls build() without withReturnType

ID

apex.correctness.formulabuilder_configuration

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Api Usage

Language

Apex

Description

Reports a Formula.builder() or FormulaEval.FormulaBuilder.builder() chain that reaches build() without calling withReturnType(…​) anywhere in the same chain. Only the chained form is reported; a builder held in a local variable and configured over several statements is not seen.

Rationale

build() validates the configuration it was given and throws FormulaEval.FormulaValidationException when it is incomplete, so a chain missing the return type compiles, deploys and passes every test that never reaches the line - and then throws the first time the formula is actually evaluated. The return type is what tells the evaluator how to coerce the result before handing it back, so leaving it out is not a defaulting question: there is no value to hand to evaluate. Dynamic formulas are usually built from configuration rather than from literals, which means the broken path is often one record type or one custom setting away from the one the test class exercised.

The following code illustrates the pattern detected by this rule:

public class DiscountFormula {

    public Object evaluateForAccount(Account account, String expression) {
        // FLAGGED: FormulaEval.FormulaBuilder chain calls build() without withReturnType
        FormulaEval.FormulaInstance instance = Formula.builder()
            .withType(Account.SObjectType)
            .withFormula(expression)
            .build();
        return instance.evaluate(account);
    }

    public Object evaluateForApexObject(Quote quote, String expression) {
        // FLAGGED: FormulaEval.FormulaBuilder chain calls build() without withReturnType
        FormulaEval.FormulaInstance instance = FormulaEval.FormulaBuilder.builder()
            .withType(Quote.class)
            .withFormula(expression)
            .build();
        return instance.evaluate(quote);
    }

    public Object evaluateConfigured(Account account, String expression) {
        FormulaEval.FormulaInstance instance = Formula.builder()
            .withType(Account.SObjectType)
            .withReturnType(FormulaEval.FormulaReturnType.BOOLEAN)
            .withFormula(expression)
            .build();
        return instance.evaluate(account);
    }

    public Object evaluateWithGlobals(Account account, String expression) {
        FormulaEval.FormulaInstance instance = FormulaEval.FormulaBuilder.builder()
            .withType(Account.SObjectType)
            .withReturnType(FormulaEval.FormulaReturnType.STRING)
            .withGlobalVariables(new List<FormulaEval.FormulaGlobal>{ FormulaEval.FormulaGlobal.PROFILE })
            .withFormula(expression)
            .build();
        return instance.evaluate(account);
    }

    public QuoteLine buildLine(Decimal amount) {
        return QuoteLine.builder()
            .withAmount(amount)
            .build();
    }
}

Remediation

Complete the chain before build(): withType(…​) for the object or Apex class the formula is evaluated against, withFormula(…​) for the expression text, and withReturnType(FormulaEval.FormulaReturnType…​.) for the type evaluate should return. Wrap the build() call in a try that catches FormulaEval.FormulaValidationException whenever the formula text comes from configuration, so a bad expression reports itself instead of failing the transaction.

Configuration

This detector does not need any configuration.

© 2026 Xygeni. All rights reserved