Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
All errors
Validation

NUMBER_OUTSIDE_VALID_RANGE: <field>: value outside of valid range

You wrote a number to a field that exceeds the field's configured precision/scale or hard-coded limits (e.g., negative on a year field). The error names the field and (usually) the value. The fix is either reshaping the field or clamping the value upstream.

Also seen asNUMBER_OUTSIDE_VALID_RANGE·value outside of valid range·Number out of range salesforce

A bulk import that loads historical revenue figures dies on row 47 with NUMBER_OUTSIDE_VALID_RANGE: Annual Revenue: value outside of valid range. The CSV's Annual Revenue value is 999,999,999,999 (almost a trillion). The Salesforce field has 16-digit precision but only 2-digit decimals; the value exceeds what fits. The platform refuses the write.

What the platform validates

Every number field in Salesforce has metadata defining its valid range:

  • Precision: the total number of digits the field can hold.
  • Scale: the number of decimal places.
  • Effective max: 10^(precision-scale) - 1.

For a Number field with precision 18 and scale 0, the max is 999,999,999,999,999,999. For precision 16, scale 2, the integer part can be at most 14 digits (16 - 2 = 14), so the max is 99,999,999,999,999.99.

Write something larger than the effective max and the platform throws NUMBER_OUTSIDE_VALID_RANGE. The same error fires for negative values when the field doesn't allow negatives, and for values whose decimal portion exceeds the field's scale.

The broken example

A migration loading legacy revenue data:

public class RevenueImporter {
    public static void importRevenue(List<Map<String, Decimal>> rows) {
        List<Account> accounts = new List<Account>();
        for (Map<String, Decimal> row : rows) {
            accounts.add(new Account(
                Id = (Id) row.get('id'),
                AnnualRevenue = row.get('revenue')
            ));
        }
        update accounts;
    }
}

If any row's revenue exceeds the field's max, the update fails. Standard Account.AnnualRevenue allows up to 999,999,999,999,999.00 (16-digit precision, 2-digit scale), so values approaching $1 quadrillion fail.

The fix: validate range against the field's metadata

public class RevenueImporter {
    public static void importRevenue(List<Map<String, Decimal>> rows) {
        Schema.DescribeFieldResult dfr = Account.AnnualRevenue.getDescribe();
        Integer precision = dfr.getPrecision();
        Integer scale = dfr.getScale();
        Decimal maxValue = Math.pow(10, (precision - scale).intValue()).subtract(0.01);

        List<Account> accounts = new List<Account>();
        List<String> errors = new List<String>();

        for (Integer i = 0; i < rows.size(); i++) {
            Decimal revenue = rows[i].get('revenue');
            if (revenue == null) continue;
            if (revenue > maxValue || revenue < 0) {
                errors.add('Row ' + i + ': revenue ' + revenue + ' is outside valid range [0, ' + maxValue + ']');
                continue;
            }
            accounts.add(new Account(
                Id = (Id) rows[i].get('id'),
                AnnualRevenue = revenue
            ));
        }

        if (!errors.isEmpty()) System.debug(LoggingLevel.WARN, errors);
        if (!accounts.isEmpty()) Database.update(accounts, false);
    }
}

The validation catches out-of-range values before save, with a useful diagnostic.

The fixed example, with policy choices

A production-shaped service that documents three failure modes:

public class RevenueImporter {
    public class Result {
        @AuraEnabled public Id accountId;
        @AuraEnabled public Boolean success;
        @AuraEnabled public Decimal originalRevenue;
        @AuraEnabled public Decimal mappedRevenue;
        @AuraEnabled public String reason;
    }

    public static List<Result> importRevenue(List<Map<String, Object>> rows) {
        Schema.DescribeFieldResult dfr = Account.AnnualRevenue.getDescribe();
        Integer precision = dfr.getPrecision();
        Integer scale = dfr.getScale();
        Decimal maxValue = computeMax(precision, scale);

        List<Account> toUpdate = new List<Account>();
        List<Result> results = new List<Result>();
        Map<Integer, Integer> rowToUpdateIdx = new Map<Integer, Integer>();

        for (Integer i = 0; i < rows.size(); i++) {
            Result r = new Result();
            r.accountId = (Id) rows[i].get('id');
            r.originalRevenue = (Decimal) rows[i].get('revenue');

            if (r.originalRevenue == null) {
                results.add(r);
                continue;
            }
            if (r.originalRevenue < 0) {
                r.success = false;
                r.reason = 'Negative revenue rejected';
                results.add(r);
                continue;
            }
            if (r.originalRevenue > maxValue) {
                r.mappedRevenue = maxValue;
                r.reason = 'Capped at max ' + maxValue;
            } else {
                r.mappedRevenue = r.originalRevenue.setScale(scale);
            }

            rowToUpdateIdx.put(i, toUpdate.size());
            toUpdate.add(new Account(Id = r.accountId, AnnualRevenue = r.mappedRevenue));
            results.add(r);
        }

        Database.SaveResult[] saveResults = Database.update(toUpdate, false);
        for (Integer rowIdx : rowToUpdateIdx.keySet()) {
            Integer updateIdx = rowToUpdateIdx.get(rowIdx);
            Result r = results[rowIdx];
            if (saveResults[updateIdx].isSuccess()) {
                r.success = true;
            } else {
                r.success = false;
                r.reason = (r.reason ?? '') + ' DML: ' + saveResults[updateIdx].getErrors()[0].getMessage();
            }
        }
        return results;
    }

    private static Decimal computeMax(Integer precision, Integer scale) {
        Decimal max = 1;
        for (Integer i = 0; i < (precision - scale); i++) max = max * 10;
        return max - Math.pow(10, -scale);
    }
}

The service decides on overflow (cap at max), negative (reject), and scale (round to field scale). The behavior is documented per result; downstream processes know exactly what was applied.

Decimal scale and rounding

A subtle case: a value with more decimal places than the field's scale is rejected, not rounded:

account.AnnualRevenue = 1234.567;   // Throws if scale is 2

The fix is to round explicitly:

account.AnnualRevenue = 1234.567.setScale(2, System.RoundingMode.HALF_EVEN);

setScale produces 1234.57 (banker's rounding). The platform accepts.

Common rounding modes:

  • HALF_EVEN: banker's rounding (default in many financial contexts).
  • HALF_UP: traditional rounding (1.5 to 2, 2.5 to 3).
  • HALF_DOWN: rounds half toward zero.
  • CEILING and FLOOR: always round up or down.
  • UP and DOWN: round away from or toward zero.

For currency values, HALF_EVEN is the IEEE standard and usually correct.

Negative-disallowed fields

Some Salesforce fields don't allow negative values (year fields, quantity fields, count fields). Writing a negative produces NUMBER_OUTSIDE_VALID_RANGE. The fix is upstream: clamp the value to zero or reject the row.

The field's "Min Value" property defines the lower bound. Custom Number fields can be configured to allow negative values; standard fields often can't. Check the field's properties in Setup before assuming.

Currency fields and multi-currency orgs

In a multi-currency org, every currency field has both a value and a currency code. The value's range depends on the field's precision and scale, not the currency. A value of 1,000,000,000 USD and 1,000,000,000 JPY both check against the same effective max.

For very large values in low-denomination currencies (JPY, IDR, VND), the field's precision may be insufficient. The fix is either to widen the field (Setup → Field → Length) or to store values in a different unit (e.g., "Thousands of JPY" instead of JPY).

Long type vs Integer type

Apex has Integer (32-bit) and Long (64-bit) primitives. Integer's max is 2,147,483,647; Long's is much larger. Apex Decimal can hold arbitrary precision (subject to platform overhead).

A surprise: Apex Integer overflow doesn't throw; it wraps around silently. Integer.MAX_VALUE + 1 is Integer.MIN_VALUE. If your code computes a value that overflows Integer, the value is wrong but doesn't error. The Salesforce field's NUMBER_OUTSIDE_VALID_RANGE fires only if the platform sees the wrapped value as out of range.

For very large values, always use Long or Decimal in Apex.

Field type vs Apex type

A subtle issue: the Apex variable type and the field's underlying type can differ. An Apex Integer (32-bit) assigned to a Number field that allows larger values fits within the field but may overflow the Integer. Apex Decimal is safer for arbitrary numeric values.

When in doubt, use Decimal in Apex and let the platform handle the conversion to the field's storage format.

Default values that exceed the field range

A scenario that bites teams: the field's "Default Value" is a formula that can produce a value exceeding the field's max. The platform doesn't validate the formula at field creation; the failure happens at save time on a record whose default computes to an oversized value.

Audit default-value formulas before deploying field changes that reduce range.

Test patterns

A test that covers happy path, negative, oversized, and decimal-overflow:

@isTest
static void importRevenue_handlesAllRanges() {
    Account a = new Account(Name = 'Test');
    insert a;

    List<Map<String, Object>> rows = new List<Map<String, Object>>{
        new Map<String, Object>{ 'id' => a.Id, 'revenue' => 1000000.50 },     // happy
        new Map<String, Object>{ 'id' => a.Id, 'revenue' => -100 },           // negative
        new Map<String, Object>{ 'id' => a.Id, 'revenue' => 1.0e15 },         // oversized
        new Map<String, Object>{ 'id' => a.Id, 'revenue' => 1000000.5678 }    // decimal overflow
    };
    Test.startTest();
    List<RevenueImporter.Result> results = RevenueImporter.importRevenue(rows);
    Test.stopTest();

    System.assertEquals(4, results.size());
    // ... assertions per result
}

The test confirms each behavior path. Future regressions surface quickly.

A common debugging surprise

A scenario that confuses people: the field's max appears to be one value when looking at it in Setup, but writes near that value fail anyway. The cause is usually one of:

Scale eats precision. A field with precision 16 and scale 2 has effective integer-part max of 14 digits. The "16" might be misread as "max 16 digits in the integer part." The platform's effective max is 10^(precision-scale), not 10^precision.

Rounding pushes into overflow. A value just under the max gets multiplied by a small factor in a formula or trigger, producing a value above the max. The trigger's intermediate value triggers the error.

Currency conversion. In multi-currency orgs, a value below the max in the user's display currency might convert to a value above the max in the org's corporate currency, where storage actually happens.

The diagnostic: compute the field's effective max from precision-scale, log the actual value being written (including any pre-save transformations), and compare. The math usually reveals which case applies.

A pattern for currency clamping

For revenue and similar financial values that should never realistically approach the field max, a clamping helper saves repeated boilerplate:

public class CurrencyGuard {
    public static Decimal clamp(Schema.SObjectField field, Decimal value) {
        if (value == null) return null;
        Schema.DescribeFieldResult dfr = field.getDescribe();
        Integer p = dfr.getPrecision();
        Integer s = dfr.getScale();
        Decimal max = computeMax(p, s);
        Decimal clamped = value;
        if (clamped > max) clamped = max;
        if (clamped < 0 && dfr.getType() == Schema.DisplayType.Currency) clamped = 0;
        return clamped.setScale(s, System.RoundingMode.HALF_EVEN);
    }
    private static Decimal computeMax(Integer p, Integer s) {
        Decimal max = 1;
        for (Integer i = 0; i < (p - s); i++) max = max * 10;
        return max - Math.pow(10, -s);
    }
}

Use it everywhere you assign to a currency or number field: account.AnnualRevenue = CurrencyGuard.clamp(Account.AnnualRevenue, computedValue);. The clamp is silent when the value is in range and corrective when it isn't.

A note on field history tracking

Fields with history tracking enabled record every change. When the platform truncates or rounds a value at save time, the history shows the final stored value, not the original input. For audit purposes, log the original input separately to a custom field or log object before save.

A common cause: external system precision

Many external systems use floating-point representations (IEEE 754 double-precision) for numeric values. These have ~15 decimal digits of precision. Salesforce's Decimal storage has arbitrary precision but with field-level limits.

When an external value like 1.23456789012345e15 arrives, the IEEE representation may have already lost precision. Salesforce stores what it receives; the stored value might differ from what the source intended. For high-precision integrations, prefer string-based numeric transport and explicit Decimal parsing.

How the error message differs by Salesforce version

Older API versions sometimes return less-specific messages (just "value outside of valid range" without the field name). Newer versions name the field explicitly. If you're integrating with a legacy API caller, the parsing logic may need to handle both formats.

For modern Apex code in API 50+, the error format is reliable. Older integrations are worth migrating to recent API versions to get cleaner diagnostics.

Storage cost considerations

Number fields with high precision (18, scale 6) use more storage per record than smaller fields. For high-volume objects (hundreds of millions of rows), the storage difference matters. Use precision and scale that fit the actual data range, not the theoretical max.

Currency fields and rounding rules

A subtle issue specific to currency fields: Salesforce normalizes currency values to the field's scale at save time, using a fixed rounding mode. The rounding can convert a borderline-valid value to an out-of-range value if the rounding pushes it over the max.

This is rare but real. For values that approach the max, validate not just the input but also the post-rounding result against the field's max.

Working with very large datasets

For datasets where revenue values legitimately span many orders of magnitude (think country-level GDP figures alongside small-business revenue), the right pattern is often to store values in a "unit-of-magnitude" field plus a "value" field. The unit tells you whether to interpret the value as thousands, millions, or billions. The value stays within the field's range.

This adds complexity but avoids the awkwardness of choosing precision/scale that fits everything.

Field-level migration impact

If you increase a field's precision or scale, existing data is preserved and reformatted automatically. If you decrease either, existing data that no longer fits is truncated or rejected (depending on the field setting). Always test field-shape changes against representative production data before deploying.

Further reading from Salesforce

Related dictionary terms

Share this fix

Share on LinkedInShare on X

Related Validation errors