التحقق من المدخلات: لا تثق بالمستخدم أبداً
في هذا الدرس 11 موضوعات
هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.
Lesson Overview
Lesson 3 ended with a polite program that gave up after one bad answer. This lesson makes it ask again. You will build the standard pattern for reading input safely: keep asking until the answer is acceptable.
There is also a larger idea in the title. A program cannot trust what comes in from outside. The user may be friendly and still type abc, press Enter on an empty line, enter -5 for a quantity, or paste a number with a stray comma. Another user may deliberately type something designed to break your program. Both cases get the same answer: check the input before you use it. This habit is the seed of an entire area of backend engineering, because web services receive input from strangers all day.
At the shop counter: if a customer says "I'll take minus three pens", the cashier does not ring that up. They say "How many?" again. Your program must be that cashier.
A note on tools. Asking again needs a decision (if) and repetition (while), taught properly in BEG-012 and BEG-013. Here you get just enough to read and run the pattern. Treat it as a preview: you are not expected to master loops today.
This lesson builds on Lessons 1 to 4, in particular hasNextInt() from Lesson 3.
Learning Objectives
By the end of this lesson you will be able to:
- Explain why input must be checked even when the user is friendly.
- Distinguish format validation (is it a number?) from range validation (is it at least 1?).
- Read and run the loop pattern that asks again until an
intin range is entered. - Explain why the bad token must be discarded with
next(), and what happens if it is not. - Reject empty text and out-of-range or special numbers such as
NaN. - Give a clear error message that tells the user what to type.
Written Explanation
Why input cannot be trusted
Input is anything that comes from outside the code you wrote: the keyboard, a file, a web request, a command-line argument. You do not control it, so assume it can be:
| Kind of problem | Example |
|---|---|
| Wrong format | abc where a number is needed |
| Wrong range | -3 or 0 as a quantity |
| Missing | the user just presses Enter |
| Surprising but valid-looking | 1e400, NaN, or Infinity in a decimal field |
| Deliberately hostile | text crafted to break something downstream |
The OWASP Input Validation Cheat Sheet, a widely used guide for developers, makes two points we use here. First, validation covers both the form of a value and whether it makes sense for the operation, and its own example is a booking whose end date must come after its start date. Second, it recommends defining what is acceptable and rejecting everything else (an allowlist), instead of trying to list every bad value.
For our small programs the allowlist is simple: a whole number from 1 to 10 means exactly that and nothing else.
Two kinds of check
- Format: can the text be read as the type I need?
hasNextInt()answers that for anint. - Range or meaning: is the value sensible? A quantity must be at least 1; an age is probably below 150; a price must be above zero.
A value can pass the first check and fail the second: -5 is a perfectly good int. Both checks are needed.
The decision: if
You saw the shape in Lesson 3. A condition inside parentheses is either true or false, and the block runs only if it is true:
if (qty >= 1) {
// runs only when qty is 1 or more
}The conditions use the comparison and logical operators of BEG-010: >=, <=, && ("and"), ! ("not").
The repetition: while (true) and break
A while loop repeats its block as long as its condition is true. while (true) never stops by itself, so we use break to leave it from the inside once the input is good:
while (true) {
// ask, read, check
// if the answer is good: break;
}Read it as "keep doing this forever, until a break is reached". It is a very common pattern for input, and BEG-013 explains loops and break in more detail.
The complete pattern
int qty;
while (true) {
System.out.print("Quantity (1-10): ");
if (in.hasNextInt()) {
qty = in.nextInt();
if (qty >= 1 && qty <= 10) {
break; // good answer: leave the loop
}
} else {
in.next(); // discard the bad token
}
System.out.println("Enter a whole number from 1 to 10.");
}Walk through it:
- Print the prompt.
hasNextInt()asks "is a whole number waiting?" without reading it. If yes, read it withnextInt().- Check the range. If it passes,
breakleaves the loop. If not, the code falls out of theifand reaches the error message, and the loop starts again. - If no whole number is waiting, discard the bad token with
in.next(), print the message, and loop again.
When the loop ends, qty is guaranteed to hold a whole number from 1 to 10. Everything after the loop can rely on that.
Why in.next() matters
hasNextInt() only looks. If it returns false, the bad token is still waiting. Without the discard, the loop asks hasNextInt() again, the same bad token is still first in line, the answer is false again, and the program prints the error message forever without waiting for the user. This is an infinite loop. A deliberately broken version, limited to three tries so it is safe to run:
int count = 0;
while (!in.hasNextInt() && count < 3) {
System.out.println("Try again");
count++;
}With abc typed, the real output is:
Try again
Try again
Try again
stopped after 3It never waited for new input, because abc was never removed. in.next() removes exactly one token: the bad one. (If the user typed several words, the loop handles them one at a time and prints the message once per bad token. That is acceptable for a first version.)
Validation with lines: text and decimals
For text fields, the check is often "not empty". The String method trim() removes spaces at both ends, and isEmpty() is true for text of length zero. (BEG-009 gave length(); isEmpty() is the same check as length() == 0.) A name made of spaces only is also empty:
String name = in.nextLine().trim();
if (name.isEmpty()) {
// ask again
}For a decimal such as a price, a tidy way to check a line is to hand the text to a small temporary scanner and ask hasNextDouble(). Here is a complete, tested loop. It uses continue, which means "skip the rest of this round and start the next round":
double price;
while (true) {
System.out.print("Price: ");
String line = in.nextLine().trim();
if (line.isEmpty()) {
System.out.println("Type a price.");
continue;
}
if (!new Scanner(line).hasNextDouble()) {
System.out.println("Not a number.");
continue;
}
price = Double.parseDouble(line);
if (price > 0) {
break;
}
System.out.println("Price must be above zero.");
}The ! means "not". new Scanner(line) makes a throw-away scanner that reads from a String instead of the keyboard. It uses the locale rules of Lesson 1, so on a comma-decimal machine test with the formats you intend to accept.
The sneaky decimals: NaN and Infinity
Decimal parsing accepts the words NaN ("not a number") and Infinity. In a compiled test, Scanner reported hasNextDouble() as true for both. Look at what comparisons do with them:
NaN > 0is false, andNaN < 0is also false. So the checkprice > 0rejects NaN, but a check written as "reject ifprice < 0" would let it through.Infinity > 0is true. A check for "above zero" alone accepts it.
This is a good reason to write the check as "accept only if it is in the range I want", the allowlist way, and to set an upper bound where there is a sensible one. A realistic price check would be price > 0 && price < 100000 or whatever your business allows.
Good error messages
A message such as Invalid input tells the user nothing. A good message says what is expected: Enter a whole number from 1 to 10.
Validation never ends at the keyboard
The same rules apply to every program that takes input from anywhere. For a web service, validation in the browser is only a convenience: OWASP's cheat sheet says client-side checks can be bypassed, so the server must validate again. Injection attacks, in which a stranger's input is treated as part of a command, depend on a program that trusted its input.
Examples
Example 1: whole number from 1 to 10 (simple)
The pattern from above as a program. With the user typing abc, 0, 11, 3.5, and finally 7:
Quantity (1-10): Enter a whole number from 1 to 10.
Quantity (1-10): Enter a whole number from 1 to 10.
Quantity (1-10): Enter a whole number from 1 to 10.
Quantity (1-10): Enter a whole number from 1 to 10.
Quantity (1-10): OK, 7(The typed answers are not echoed here, because the test read them from a file.) abc fails the format test, 0 and 11 fail the range test, 3.5 fails the format test (it is not a whole number), and 7 passes.
Example 2: a validated price from a line (practical)
The price loop from the Written Explanation, tested with the lines: (empty), free, -2, 0, 2.5:
Price: Type a price.
Price: Not a number.
Price: Price must be above zero.
Price: Price must be above zero.
Price: price=2.5Each bad case gets its own message, and the program asks until it has a positive number. Note that the empty line and free are caught before Double.parseDouble runs, so the program never crashes.
Example 3: what NaN does (realistic)
A test program that reads one decimal and compares it:
double d = in.nextDouble();
System.out.println(d + " " + (d > 0) + " " + (d < 0));| User types | Output | Meaning |
|---|---|---|
NaN | NaN false false | neither positive nor negative |
Infinity | Infinity true false | passes a "greater than zero" check |
This is why a real validation uses a closed range, not only "not negative". A price of NaN would otherwise slip into your totals, and every sum it touches becomes NaN.
Backend Insight
Every layer of a backend that receives data asks the same questions you just coded: Is the format right? Is the value in range? Is it allowed at all? Java teams later use Bean Validation, which lets you write the rules as annotations (@Min(1), @NotBlank) instead of loops. The rules you write now are those rules in plain code. Learn them here and the annotations will be easy.
Two habits to keep:
- Validate at the border. Check input where it enters the program, so that the rest of the code can rely on it.
- Reject, do not repair, when unsure. Silently turning
abcinto0hides the problem and produces wrong results later.
Common Mistakes
Mistake 1: Not discarding the bad token
Forgetting in.next() causes an infinite loop that prints the error message endlessly.
Mistake 2: Checking format but not range
nextInt() accepts -5 and 0. For a quantity, add qty >= 1.
Mistake 3: Silent defaults
Using 0 or 1 when the input is bad hides errors. Ask again, with a message that says what is expected, not just "Error!".
Mistake 4: Checking only "not negative"
NaN is not negative and not positive. Accept only what you intend: a closed range.
Exercises
Try each exercise yourself before opening the solutions.
Level A: Foundation
A1. Give two reasons to validate input from a friendly user.
أظهر الحل A1
A1. Any two of: people mistype; people misunderstand the question (a word instead of a number); a value may be valid in format but not sensible (-5 seats); input from other programs or attackers may be wrong or hostile.
A2. Which check is a format check and which is a range check: (a) hasNextInt(); (b) age >= 0 && age <= 120?
أظهر الحل A2
A2. (a) format; (b) range.
A3. In the loop pattern, what does in.next() do inside the else branch, and what would happen without it?
أظهر الحل A3
A3. It reads and discards one token, the one that was not a whole number. Without it, hasNextInt() would keep returning false for the same token, and the loop would repeat endlessly without waiting for new input.
Level B: Application
B1. Write a loop that asks for a percentage and accepts only a whole number from 0 to 100.
أظهر الحل B1
B1.
int pct;
while (true) {
System.out.print("Percent (0-100): ");
if (in.hasNextInt()) {
pct = in.nextInt();
if (pct >= 0 && pct <= 100) {
break;
}
} else {
in.next();
}
System.out.println("Enter a whole number from 0 to 100.");
}B2. Write a loop that asks for a name and keeps asking while the name is empty or only spaces.
أظهر الحل B2
B2.
String name;
while (true) {
System.out.print("Name: ");
name = in.nextLine().trim();
if (!name.isEmpty()) {
break;
}
System.out.println("The name cannot be empty.");
}If you used nextInt() earlier in the program, remember the throw-away nextLine() from Lesson 2 before this loop.
B3. Rewrite the error message Wrong! so that it helps a user who typed -1 as the number of seats (valid seats: 1 to 4).
أظهر الحل B3
B3. For example: Seats must be a whole number from 1 to 4. It says what is valid.
Level C: Challenge
C1. Write a loop for a decimal temperature between -50 and 60 (inclusive). It must reject text, and also NaN and Infinity. Explain why your range check rejects the two special values.
أظهر الحل C1
C1.
double temp;
while (true) {
System.out.print("Temperature (-50 to 60): ");
if (in.hasNextDouble()) {
temp = in.nextDouble();
if (temp >= -50 && temp <= 60) {
break;
}
} else {
in.next();
}
System.out.println("Enter a number from -50 to 60.");
}NaN fails every comparison, so temp >= -50 is false and it is rejected. Infinity is larger than 60 and fails temp <= 60. (Reasoned from the compiled NaN/Infinity test of Example 3; this exact loop was not run.)
C2. The user types 5 abc 7 on one line for a loop that wants one whole number in 1..10. Trace what the quantity loop does: which tokens are read, and what is qty at the end?
أظهر الحل C2
C2. The first pass: hasNextInt() is true, nextInt() reads 5, and the range check passes, so the loop ends with qty = 5. The remaining text abc 7 stays in the input for the next read. This is the same leftover issue as Lesson 2: a later nextLine() would return abc 7.
Mini Challenge
Write Seat booking. Ask for a name (line, must not be empty), a number of seats (whole number 1 to 4), and a ticket price (decimal, above zero and below 1000). Re-ask with a specific message for each failure. Then print one confirmation line with printf. Test it with: an empty name, abc seats, 0 seats, 5 seats, -3 price, NaN price, and finally good values.
Check your work: no input crashes the program; each loop re-asks with a message that says what is valid; the leftover line after the seats number does not skip the name or the price.
Summary & Next Lesson
Key takeaways
- Treat all outside input as untrustworthy: friendly users make mistakes, and hostile users exploit them.
- Check format (
hasNextInt(),hasNextDouble()) and range (qty >= 1 && qty <= 10). - Pattern: loop, prompt, check,
breakon success; on a bad token discard it within.next(). - Accept only what you intend: a closed range also rejects
NaNandInfinity. - Give messages that say what is expected. Validate at the border, then rely on the value.
Next lesson
The last lesson, "Demo: building the receipt printer with input validation", combines reading, validating, and formatting into one program, and it prepares you for the module lab.
Self-check
- Why is
-5a problem even thoughhasNextInt()returns true for it? - What does
in.next()do in the else-branch? - What is an infinite loop, and how does forgetting
in.next()cause one? - Why is "reject if negative" not enough for decimals?
- What makes a good error message?
أظهر الإجابات
- It is a valid
int(format), but not a valid quantity (range). - It discards the bad token so the next check sees fresh input.
- A loop that never ends. Without
next(), the same bad token is checked again and again. NaNis not negative, so it passes a "not negative" check. Use an allowed range.- It says what is expected, ideally with an example, such as the valid range.
References
- OWASP Cheat Sheet Series, Input Validation Cheat Sheet. Supports checking both type/format and meaning, preferring allowlists, and validating on the server even when the browser also checks.
- Oracle, Java SE 21 API, Scanner. Supports
hasNextInt()not advancing the scanner and locale-dependent number reading. - Oracle, Java SE 21 API, Double.parseDouble. Supports acceptance of
NaNandInfinityas valid text. - The loops, the
NaNandInfinitycomparisons, and the printed transcripts were compiled and run with JDK 21. The description of Bean Validation annotations and of injection attacks is general background that was not checked against a page here.
التطبيق
طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.
تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.