🔴 The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
at java.base/java.lang.Integer.parseInt(Integer.java:652)
at java.base/java.lang.Integer.parseInt(Integer.java:770)
at com.devinhyderabad.orders.QuantityParser.parse(QuantityParser.java:10)
at com.devinhyderabad.orders.OrderMain.main(OrderMain.java:7)⚡ Quick Fix Works 80% of the time
Trim and validate the string, then parse inside a catch that converts the crash into a domain result — never let a raw NFE cross service boundaries.
int quantity;
try {
quantity = Integer.parseInt(raw.trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("quantity not numeric: [" + raw + "]");
}🧠 Why this Happens
Tap to expand the deep technical explanation
parseInt walks the string character-by-character accumulating digit values through Character.digit(c, radix); the first non-digit after the optional sign triggers throw new NumberFormatException("For input string: "" + s + """). A null reference short-circuits BEFORE the character walk into NumberFormatException("null") — deliberately an NFE rather than NPE because invalid input, absence included, is part of the method contract. The exception quotes its offending argument verbatim because the payload IS the diagnostic: nothing about the JVM state matters, only what the caller fed in.
The HITEC City Parking Spot Analogy:
A cashier reading a handwritten order slip stops mid-count at "12a" and slides the slip back with the scribble circled — the quoted string in the message is that circle.
🔁 How to Reproduce Confirm this is your error
Call Integer.parseInt("abc"). Message wording has been identical since Java 5 on every build — canonical format shown above; frame LINE NUMBERS differ per version because Integer.parseInt appears twice (the public method delegates to the package-private radix variant). Not lab-captured by design: stable wording, no version split.
🛠️ Solutions (5 Ways to Fix)
Wrap parsing in a tryParse helper returning Optional
👉 Use this whenever the value originates outside the JVM — forms, query params, CSV cells, queue payloads.
One utility converts parse failures into an explicit empty result so callers choose the fallback instead of crashing. Null/blank handling lives HERE once, not scattered across forty call sites.
public static OptionalInt tryParse(String raw) {
if (raw == null || raw.isBlank()) return OptionalInt.empty();
try {
return OptionalInt.of(Integer.parseInt(raw.trim()));
} catch (NumberFormatException e) {
return OptionalInt.empty();
}
}Read the message shapes and triage in seconds
👉 Use this to decode the pasted log immediately.
For input string: "12a" means a stray character — including INVISIBLE ones like full-width digits from CJK keyboards, which look identical to ASCII digits. Empty quotes mean blank-after-trim. A bare null means the reference itself was null: parseInt(null) deliberately throws NumberFormatException with message null rather than NPE, because invalid input including absence is part of its documented contract.
Integer.parseInt(null); // NumberFormatException: null
Integer.parseInt(""); // For input string: ""
Integer.parseInt("12"); // full-width digits -> For input string: "12"
Double.parseDouble("1,5"); // locale decimal comma -> For input string: "1,5"Handle doubles, locales, and human decimals properly
👉 Use this for human-entered decimal values rather than machine formats.
parseDouble silently accepts "Infinity" and "NaN" — alarming when the field is a price. European "1.234,50" needs Locale-aware NumberFormat, never blind comma-stripping ("1,500" could be thousands OR decimal). Round-trip through BigDecimal for money.
NumberFormat de = NumberFormat.getNumberInstance(Locale.GERMANY);
Number parsed = de.parse("1.234,50"); // -> 1234.50
BigDecimal price = new BigDecimal(parsed.toString());Overflow masquerades as format errors
👉 Use this when the failing string LOOKS like a perfectly good number.
parseInt("99999999999") fails not because of characters but because the value exceeds Integer.MAX_VALUE (2147483647) — same exception class, different root cause. Escalate the type to long or BigInteger instead of "fixing" the format. Leading "+" has been legal since Java 7.
Long.parseLong("99999999999"); // fits in long
new BigInteger("12345678901234567890123"); // arbitrary precision
Integer.parseInt("+42"); // legal since Java 7Structured formats deserve structured parsers
👉 Use this when numbers arrive inside JSON, CSV, or config rather than as standalone strings.
Hand-splitting CSV invites NFE plus silent column-shift bugs. Jackson reports the exact JSON path of a bad numeric field; opencsv reports row and column. Parse typed values once at the boundary and pass them inward.
record Order(int qty, String sku) {}
Order o = mapper.readValue(json, Order.class);
// Bad number surfaces with context:
// Cannot deserialize value of type `int` from String "abc"
// at [Source: ...] (through reference chain: Order["qty"])📋 Version Notes
Message shapes identical to today ("For input string:", bare null for null input); Character.digit-based parsing loop unchanged.
No wording change; traces from this era still dominate forum posts — their frame line numbers will not match yours.
Unchanged semantics; Integer.parseInt still delegates public(String) -> package-private(String,int).
Unchanged; only stack-frame line numbers drift between builds.
🛡️ How to Prevent This Next Time
Validate wire/UI strings once at the edge, centralize parsing behind a tryParse-style helper, unit-test the trio null/empty/whitespace explicitly, and prefer typed transports over stringly-typed channels.