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.CEILINGandFLOOR: always round up or down.UPandDOWN: 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
Related Validation errors
DUPLICATE_VALUE: duplicate value found
ValidationYou tried to insert or update a record with a value on a unique field (External ID set to "Unique" or one of the unique standard fields like…
ENTITY_IS_DELETED: entity is deleted
ValidationYou tried to update or query a record that's in the recycle bin (soft-deleted). Either undelete it first, query with `ALL ROWS` to include d…
ENTITY_IS_LOCKED: entity is locked for approval
ValidationThe record is currently in an Approval Process and is locked for editing until the approval is granted, rejected, or recalled. The lock is e…
FIELD_CUSTOM_VALIDATION_EXCEPTION: <your validation rule's error message>
ValidationA validation rule on the object evaluated to TRUE for the record being saved, blocking the save. The text after the colon is the rule's own …
INACTIVE_OWNER_OR_USER: operation performed with inactive User
ValidationYou set a record's Owner (or another User reference) to a deactivated user. Salesforce blocks new ownership pointing at inactive users by de…