DecimalFormat parses locale-dependent numbers, and ParsePosition exposes how much of the input was consumed.
Java DecimalFormat: reject trailing input and pin the locale
This complete program targets Java 8. Its displayed output is checked by the tutorial validation script.
Require the whole field
An invoice amount parser must reject 125.50INR when its contract accepts a numeric field only. DecimalFormat can parse a valid prefix, so getting a Number is weaker than validating the whole field. Check the final parse index against input length and reject an empty or failed parse.
The program fixes US symbols, disables grouping and requests BigDecimal output. It then applies scale two with UNNECESSARY rounding, making fractional precision a visible boundary. Locale-dependent punctuation is a user interface policy; a machine API should define its own numeric grammar.
Parsing is not complete domain validation
A positive amount and a finite scale still do not identify a currency. Store the currency or minor-unit contract separately. Locale data is not evidence that every textual variation is acceptable. If the input must match a narrow ASCII wire format, validate that grammar explicitly before DecimalFormat.
DecimalFormat is mutable. This fixture creates it locally rather than putting one parser in a singleton service. Shared parser reuse can turn a predictable format rule into a thread-safety problem. BigDecimal arithmetic handles later calculations; Typed configuration handles startup values.
Working program
import java.math.*;
import java.text.*;
import java.util.Locale;
public class InvoiceAmountInput {
static BigDecimal parse(String input) {
if (input.length() > 32) throw new IllegalArgumentException("amount too long");
DecimalFormat format = new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.US));
format.setGroupingUsed(false); format.setParseBigDecimal(true);
ParsePosition position = new ParsePosition(0);
Number parsed = format.parse(input, position);
if (!(parsed instanceof BigDecimal) || position.getIndex() != input.length())
throw new IllegalArgumentException("complete decimal required");
BigDecimal amount = ((BigDecimal) parsed).setScale(2, RoundingMode.UNNECESSARY);
if (amount.signum() <= 0) throw new IllegalArgumentException("positive amount required");
return amount;
}
public static void main(String[] args) {
System.out.println(parse("125.50"));
try { parse("125.50INR"); }
catch (IllegalArgumentException rejected) { System.out.println("suffix rejected"); }
}
}Output
125.50
suffix rejectedCosts and boundaries
Parsing and BigDecimal allocation grow with the digit count, bounded here by 32 characters. Creating a local formatter avoids shared mutable state at an allocation cost. This parser deliberately does not implement every currency or strict ASCII notation.
Common Mistakes
- Do not treat a valid prefix as a valid amount field.
- Pin the locale instead of inheriting the machine default.
- Do not share DecimalFormat without an ownership policy.
Read next
Java BigDecimal: decimal amounts and explicit rounding, Java ResourceBundle: locale fallback and missing message keys, Spring Boot configuration properties: bind values and reject bad startup input.
More numeric representation boundaries
Continue with Java Double.isFinite: reject Infinity and NaN at input boundaries.
