🔴 The Error You're Seeing

Confirm this matches your console output. If it does, you're in the right place.

ERROR LOGException 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)

Solution 1✓ Most common cause

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(); } }
Solution 2

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"
Solution 3

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());
Solution 4

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 7
Solution 5

Structured 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

Java 8

Message shapes identical to today ("For input string:", bare null for null input); Character.digit-based parsing loop unchanged.

Java 11

No wording change; traces from this era still dominate forum posts — their frame line numbers will not match yours.

Java 17

Unchanged semantics; Integer.parseInt still delegates public(String) -> package-private(String,int).

Java 21

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.