So far your programs have been monologues: the data was hard-coded and the output was limited to concatenating strings with +, producing amounts like 13.875 EUR that no receipt would accept. This lesson closes both gaps. You will learn to format output professionally with printf (alignment, widths, controlled decimals) and to read data from the user with Scanner, including the famous problem of mixing nextInt() with nextLine() that baffles everyone the first time. By the end, BiblioTech will be able to converse with whoever uses it and print a book card that looks like a real document.
All the code in this lesson is linear: no conditionals or loops, which arrive in module 2.
Contents
- Standard output:
System.out printversusprintlnprintfand the format specifiers- Widths, alignment and other modifiers
String.format: the same formatting, in a variableSystem.errand when to use it- Reading data with
Scanner - The reading methods and what each one returns
- The classic problem: mixing
nextInt()withnextLine() - Closing the
Scanner - Converting text to numbers:
parseIntandparseDouble - The BiblioTech book card
- Common Mistakes and Tips
- Exercises
- Standard output:
System.out
System.outSystem.out is an object representing standard output: normally, the console. It is always available with no import at all, because the System class belongs to java.lang.
Let's break down the expression you have been writing since the first lesson:
Systemis a class injava.langthat gives access to system resources.outis a field of that class: aPrintStreamobject connected to the console.printlnis a method of that object.
The three methods you will use:
| Method | What it does |
|---|---|
print(x) |
Writes x without a line break |
println(x) |
Writes x and breaks the line |
printf(format, args...) |
Writes with controlled formatting, no automatic line break |
print versus println
print versus printlnThe only difference is the trailing line break, but it changes the composition of the output a lot:
System.out.print("Title: ");
System.out.print("Effective Java");
System.out.println(); // println() with no arguments just breaks the line
System.out.println("ISBN: 978-0000000001");Output:
Both methods accept any type: String, numbers, boolean, char. Java automatically converts the value to text.
System.out.println(2018); // 2018
System.out.println(3.75); // 3.75
System.out.println(true); // true
System.out.println('J'); // JAnd they accept concatenation with +, which is what you have been using:
String title = "Effective Java";
int publicationYear = 2018;
System.out.println("Book: " + title + " (" + publicationYear + ")");This style works, but it becomes unreadable as soon as there are four or five variables, and it gives you no control over the format: you cannot say "show me this double with exactly two decimals". That is what printf is for.
printf and the format specifiers
printf and the format specifiersprintf (from print formatted) takes a format string with markers starting with %, followed by the values that will fill those markers, in order.
String title = "Effective Java";
double fine = 13.875;
System.out.printf("The book %s has accrued a fine of %.2f EUR%n", title, fine);Output:
Look at what just happened: 13.875 was shown as 13.88, rounded to two decimals. That control is exactly what was missing in the previous lesson.
The essential specifiers
| Specifier | Type it expects | Example of use | Result |
|---|---|---|---|
%s |
Anything (text) | printf("%s", "Effective Java") |
Effective Java |
%d |
Integer (int, long) |
printf("%d", 2018) |
2018 |
%f |
Decimal (double, float) |
printf("%f", 3.5) |
3.500000 |
%.2f |
Decimal with 2 decimals | printf("%.2f", 13.875) |
13.88 |
%b |
Boolean | printf("%b", true) |
true |
%c |
Character | printf("%c", 'J') |
J |
%n |
Portable line break | printf("Done%n") |
breaks the line |
%% |
A literal % symbol |
printf("100%%") |
100% |
Three critical points:
%fshows SIX decimals by default.printf("%f", 3.5)prints3.500000. You will almost always want%.2f.- Use
%n, not\n.%nproduces the line break native to the operating system (\non Unix,\r\non Windows);\nalways produces the Unix one. On the console you barely notice, but it is the correct practice. printfdoes not break the line on its own. If you forget the trailing%n, the next piece of output will stick to the previous one.
The decimal separator detail
printf respects the system's regional settings. On an English-language system the decimal separator is the dot, so %.2f prints 13.88; on a machine configured for Spanish or German it would print 13,88. If you need one specific separator deterministically (to generate a CSV file, for example), you can state the locale explicitly:
import java.util.Locale;
System.out.printf(Locale.US, "%.2f%n", 13.875); // 13.88, always with a dotIt is a nuance worth knowing now so that the output on your machine does not surprise you.
Several values at once
The values are consumed in order, left to right:
String employee = "Marta Ruiz";
String title = "Effective Java";
int daysLate = 12;
double fine = 3.0;
System.out.printf("%s has %s %d days overdue: %.2f EUR%n",
employee, title, daysLate, fine);Output:
If the number of markers does not match the number of values, or the types do not fit, the program fails at runtime with MissingFormatArgumentException or IllegalFormatConversionException. The most frequent case is using %d for a double: %d is only for integers.
- Widths, alignment and other modifiers
This is where printf stops being a convenience and becomes a tool: it can align columns.
The full structure of a specifier is:
| Modifier | Meaning | Example | Result (with | marking the edges) |
|---|---|---|---|
%10s |
Width 10, aligned right | printf("|%10s|", "Java") |
| Java| |
%-10s |
Width 10, aligned left | printf("|%-10s|", "Java") |
|Java | |
%5d |
Integer in width 5, right-aligned | printf("|%5d|", 12) |
| 12| |
%-5d |
Integer in width 5, left-aligned | printf("|%-5d|", 12) |
|12 | |
%05d |
Pads with zeros on the left | printf("%05d", 42) |
00042 |
%8.2f |
Total width 8, 2 decimals | printf("|%8.2f|", 13.875) |
| 13.88| |
%,d |
Thousands separator | printf("%,d", 1250000) |
1,250,000 |
%+d |
Always shows the sign | printf("%+d", 12) |
+12 |
Points to remember:
- The width is a minimum, not a maximum. If the text is longer, it is shown in full and overflows the column.
- The hyphen turns on left alignment. Without it, everything is right-aligned.
- The usual convention: text on the left, numbers on the right. Right-aligned numbers let you compare magnitudes at a glance, because the units line up in the same column.
Applied to a table from the BiblioTech catalog:
System.out.printf("%-22s %-16s %6s %10s%n", "TITLE", "AUTHOR", "YEAR", "FINE");
System.out.println("-".repeat(58));
System.out.printf("%-22s %-16s %6d %10.2f%n", "Effective Java", "Joshua Bloch", 2018, 3.0);
System.out.printf("%-22s %-16s %6d %10.2f%n", "Design Patterns", "Erich Gamma", 1994, 13.88);
System.out.printf("%-22s %-16s %6d %10.2f%n", "Refactoring", "Martin Fowler", 1999, 0.0);Output:
TITLE AUTHOR YEAR FINE ---------------------------------------------------------- Effective Java Joshua Bloch 2018 3.00 Design Patterns Erich Gamma 1994 13.88 Refactoring Martin Fowler 1999 0.00
With println and concatenation, this alignment would be practically impossible. And notice the "-".repeat(58) trick: String's repeat method (Java 11+) repeats a string, perfect for separator lines.
String.format: the same formatting, in a variable
String.format: the same formatting, in a variableSometimes you do not want to print the text, but to store it. String.format uses exactly the same syntax as printf but returns a String instead of writing it:
double fine = 13.875;
String receiptLine = String.format("Accrued fine: %.2f EUR", fine);
System.out.println(receiptLine);The following two forms are equivalent:
System.out.printf("Fine: %.2f EUR%n", fine);
System.out.println(String.format("Fine: %.2f EUR", fine));String.format is the option when the formatted text has to be reused: saved to a file (module 7), sent over the network (module 9) or used to compose a message that will be shown later.
System.err and when to use it
System.err and when to use itAlongside standard output there is standard error output, System.err, with the same methods:
System.out.println("Loan registered successfully");
System.err.println("WARNING: the ISBN entered does not have the expected format");In the IDE console, System.err usually appears in red. But its real reason to exist is that they are two independent channels that the operating system can redirect separately:
# Only the normal output goes to the file; errors are still shown on screen
java BiblioTechApp > receipts.txt
# Each channel to a different file
java BiblioTechApp > receipts.txt 2> errors.logThis is what lets an automated process keep the clean data in one place and the problems in another.
| Channel | Used for | BiblioTech example |
|---|---|---|
System.out |
Results, normal information | The loan receipt |
System.err |
Errors, warnings, diagnostics | "The catalog could not be read" |
A technical nuance: System.err is unbuffered and System.out is buffered, so when you mix them the messages can appear out of order on screen. It is not your fault.
In professional applications, System.err ends up replaced by a logging system with levels (INFO, WARN, ERROR), which is studied in lesson 06-07. For module 1, System.err is enough.
- Reading data with
Scanner
ScannerScanner is the standard class for reading user input. It lives in java.util, so it needs an import:
import java.util.Scanner;
public class BiblioTechInput {
public static void main(String[] args) {
// We create a Scanner connected to standard input (the keyboard)
Scanner input = new Scanner(System.in);
System.out.print("Employee name: ");
String employee = input.nextLine();
System.out.println("Welcome to BiblioTech, " + employee);
input.close();
}
}Let's analyse the key line:
new Scanner(...)creates a new object.newis the object creation operator; you will study it in depth in module 3, but you already need it here.System.inis standard input: the keyboard, the counterpart ofSystem.outfor output.inputis the name we give it. It could besc,keyboardorreader; it should be descriptive.
A usability detail: to ask for data you use System.out.print (without ln), so that the cursor stays on the same line as the question. It is a small detail with a big impact on how polished the program feels.
- The reading methods and what each one returns
| Method | Returns | What it reads |
|---|---|---|
nextLine() |
String |
The whole line, spaces included, up to the Enter |
next() |
String |
A single word (up to the first space) |
nextInt() |
int |
The next integer |
nextDouble() |
double |
The next decimal |
nextBoolean() |
boolean |
true or false |
hasNextInt() |
boolean |
Is the next thing an integer? It consumes nothing |
hasNextLine() |
boolean |
Is there another line left? It consumes nothing |
The difference between nextLine() and next() matters a lot in BiblioTech, where titles and names contain spaces:
System.out.print("Book title: ");
String title = input.nextLine(); // the whole "Effective Java"
System.out.print("Book title: ");
String word = input.next(); // only "Effective"; "Java" stays pendingPractical rule: for names and titles, always use nextLine().
The hasNextXxx() methods are special: they ask without consuming. They are for checking whether what is coming is of the expected type before reading it:
System.out.print("Elapsed days: ");
boolean isValidNumber = input.hasNextInt();
System.out.println("Is the input an integer? " + isValidNumber);Their full usefulness requires conditionals (module 2) to react to the result, and loops to keep insisting until you get valid data (lesson 02-02). For now just remember that they exist and what they do.
- The classic problem: mixing
nextInt() with nextLine()
nextInt() with nextLine()This is the bug absolutely everyone runs into while learning Java. Watch:
Scanner input = new Scanner(System.in);
System.out.print("Elapsed days: ");
int days = input.nextInt();
System.out.print("Employee name: ");
String employee = input.nextLine(); // <- DOES NOT WAIT: it skips the question
System.out.println("[" + employee + "]"); // prints []The program asks for the days, and then shows the second question and carries straight on without letting you type anything. employee ends up empty.
Why it happens
Scanner does not read "by questions", but from a continuous stream of characters. When you type 27 and press Enter, that stream contains:
nextInt() consumes the characters of the number (27) and stops just before the \n, leaving it in the stream. Then nextLine() does exactly its job: it reads up to the next line break. Since the break is right there, it returns an empty string immediately, without waiting for anybody.
flowchart TD
A["The user types: 27 + Enter"] --> B["Input stream: '27\\n'"]
B --> C["nextInt() consumes '27'<br/>and LEAVES the '\\n' pending"]
C --> D["nextLine() finds the '\\n'<br/>and returns an empty string without waiting"]
D --> E["It looks like the program<br/>has skipped the question"]
The two solutions
Solution 1: consume the pending break with an extra nextLine().
System.out.print("Elapsed days: ");
int days = input.nextInt();
input.nextLine(); // consumes the \n left by nextInt()
System.out.print("Employee name: ");
String employee = input.nextLine(); // now it DOES waitSolution 2 (the recommended one): read everything with nextLine() and convert.
System.out.print("Elapsed days: ");
int days = Integer.parseInt(input.nextLine()); // reads the WHOLE line and converts it
System.out.print("Employee name: ");
String employee = input.nextLine(); // works with no surprisesA comparison of both approaches:
| Approach | Advantages | Drawbacks |
|---|---|---|
nextInt() + extra nextLine() |
Less typing | Easy to forget; you have to remember where to put it |
Only nextLine() + parseInt |
Consistent and predictable; one method for everything | One conversion call per value |
This course adopts the second option in BiblioTechApp. When all reading goes through nextLine(), the problem disappears by construction and the code is uniform.
- Closing the
Scanner
ScannerScanner is a system resource and must be closed when it is no longer needed:
Put it at the end of main. If you do not, the compiler and the IDE will show a "resource leak" warning.
An important warning: closing a Scanner created over System.in also closes System.in. From that moment on you cannot create another Scanner to read from the keyboard in the same program. That is why the rule is: a single Scanner for the whole application, created at the start and closed at the end.
In module 6 you will see try-with-resources, which closes resources automatically and is the professional way to do it.
- Converting text to numbers:
parseInt and parseDouble
parseInt and parseDoubleEverything arriving from the console is text, even if the user types digits. To operate on it you have to convert it:
String daysText = "27";
int days = Integer.parseInt(daysText); // 27 as a number
String rateText = "0.25";
double rate = Double.parseDouble(rateText); // 0.25 as a numberThat is why these two lines behave so differently:
System.out.println("27" + 15); // "2715" -> text concatenation
System.out.println(Integer.parseInt("27") + 15); // 42 -> numeric additionWhat happens if the text is not a number
The program stops with a runtime error:
Exception in thread "main" java.lang.NumberFormatException: For input string: "twenty-seven" at java.base/java.lang.Integer.parseInt(Integer.java:668) at BiblioTechApp.main(BiblioTechApp.java:12)
This is an exception, Java's mechanism for signalling runtime errors. Catching them and responding gracefully (asking for the value again, showing a friendly message) is the subject of module 6. For now, hold on to two ideas:
- You know what that error means if it shows up.
- The programs in this module assume the user enters correct data. It is a real limitation that we will explicitly acknowledge in the next lesson.
A useful detail: Double.parseDouble expects the dot as the decimal separator, never a comma. Double.parseDouble("0,25") fails; Double.parseDouble("0.25") works. It is the mirror image of the printf asymmetry, and it is worth keeping in mind when testing your programs.
- The BiblioTech book card
Let's put it all together. This program asks for the details of a catalog book on the console and prints a formatted card:
import java.util.Scanner;
public class BiblioTechBookCard {
public static void main(String[] args) {
// A single Scanner for the whole application.
Scanner input = new Scanner(System.in);
// --- Business constants ---
final String COMPANY_NAME = "Nexus Software";
final int CURRENT_YEAR = 2026;
final int CARD_WIDTH = 46;
// --- Welcome header ---
System.out.println("=".repeat(CARD_WIDTH));
System.out.printf(" BiblioTech - %s%n", COMPANY_NAME);
System.out.println(" Add a book to the catalog");
System.out.println("=".repeat(CARD_WIDTH));
System.out.println();
// --- Data collection ---
// ALL reading is done with nextLine() to avoid the pending
// line break problem that nextInt() leaves behind.
System.out.print("Book title : ");
String title = input.nextLine();
System.out.print("Author : ");
String author = input.nextLine();
System.out.print("ISBN : ");
String isbn = input.nextLine();
System.out.print("Year of publication: ");
int publicationYear = Integer.parseInt(input.nextLine());
System.out.print("Number of pages : ");
int pages = Integer.parseInt(input.nextLine());
System.out.print("Purchase price : ");
double price = Double.parseDouble(input.nextLine());
// --- Derived calculations ---
int age = CURRENT_YEAR - publicationYear;
double pricePerPage = price / pages; // double / int -> double
boolean available = true; // every new book enters as available
// --- Formatted card ---
System.out.println();
System.out.println("=".repeat(CARD_WIDTH));
System.out.println(" CATALOG CARD");
System.out.println("=".repeat(CARD_WIDTH));
System.out.printf("%-20s %s%n", "Title:", title);
System.out.printf("%-20s %s%n", "Author:", author);
System.out.printf("%-20s %s%n", "ISBN:", isbn);
System.out.printf("%-20s %d%n", "Published:", publicationYear);
System.out.printf("%-20s %d years%n", "Age:", age);
System.out.printf("%-20s %,d%n", "Pages:", pages);
System.out.printf("%-20s %8.2f EUR%n", "Price:", price);
System.out.printf("%-20s %8.4f EUR%n", "Price/page:", pricePerPage);
System.out.printf("%-20s %b%n", "Available:", available);
System.out.println("=".repeat(CARD_WIDTH));
System.err.println("[info] Card generated without data validation.");
input.close();
}
}A sample run (what the user types appears after the colons):
============================================== BiblioTech - Nexus Software Add a book to the catalog ============================================== Book title : Effective Java Author : Joshua Bloch ISBN : 978-0000000001 Year of publication: 2018 Number of pages : 416 Purchase price : 45.9 ============================================== CATALOG CARD ============================================== Title: Effective Java Author: Joshua Bloch ISBN: 978-0000000001 Published: 2018 Age: 8 years Pages: 416 Price: 45.90 EUR Price/page: 0.1103 EUR Available: true ==============================================
Five design decisions worth pointing out:
- A single
Scanner, created at the start and closed at the end. - All reading with
nextLine(), with explicit conversion. It is never mixed withnextInt(). System.out.printfor the questions, so that the cursor stays on the line.%-20son the labels, which creates a perfectly aligned left column.%8.2ffor the amounts: two decimals and a fixed width, so that the figures line up on the right.
Common Mistakes and Tips
- Forgetting
import java.util.Scanner;. It producescannot find symbol: class Scanner. It is the import you will forget most often in the whole course. - Mixing
nextInt()withnextLine(). The program skips questions. UsenextLine()for everything and convert withparseInt. - Using
next()to read titles or names. It cuts off at the first space: "Marta Ruiz" becomes "Marta". - Using
%dfor adouble. It throwsIllegalFormatConversionException.%dis only for integers; for decimals,%f. %fshowing six decimals. That is its default behaviour; always specify the precision:%.2f.- Forgetting the
%nat the end of aprintf. All the lines stick together. - Creating a new
Scannerafter closing another one overSystem.in. It does not work:System.inis closed. One singleScannerper program. - Entering
0,25instead of0.25.Double.parseDoubleexpects the dot and throwsNumberFormatExceptionwith the comma. - Tip: define constants for the format widths (
CARD_WIDTH) instead of repeating numbers. Changing the layout becomes changing one line. - Tip:
"=".repeat(46)is far cleaner than typing 46 equals signs by hand, and you will not miscount them. - Tip: when an output does not line up, print a reference line with
|at the edges (printf("|%-20s|%n", text)) to see exactly where the columns fall.
Exercises
Exercise 1: Predict the behaviour
Analyse this program without running it. State what is printed, what data it actually manages to read and why. Then write two corrected versions: one using the extra nextLine() technique and another reading everything with nextLine().
import java.util.Scanner;
public class ScannerTest {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("Elapsed days: ");
int days = input.nextInt();
System.out.print("Employee name: ");
String employee = input.nextLine();
System.out.print("Book title: ");
String title = input.nextLine();
System.out.println("Employee: [" + employee + "]");
System.out.println("Title: [" + title + "]");
System.out.println("Days: " + days);
input.close();
}
}Exercise 2: An aligned catalog table
Write the class CatalogTable that prints, without asking the user for anything, exactly the following table with the three BiblioTech books. Use only printf and "-".repeat(...).
+------------------------+------------------+-------+------------+ | TITLE | AUTHOR | YEAR | PRICE | +------------------------+------------------+-------+------------+ | Effective Java | Joshua Bloch | 2018 | 45.90 | | Design Patterns | Erich Gamma | 1994 | 52.00 | | Refactoring | Martin Fowler | 1999 | 38.75 | +------------------------+------------------+-------+------------+ | TOTAL | | 136.65 | +------------------------+------------------+-------+------------+
Hint: define constants with the widths of each column and build the separator line with repeat.
Exercise 3: Return record
Write the class ReturnRecord that asks on the console for:
- The employee's name
- The book title
- The ISBN
- The days elapsed since the loan
- The daily rate in euros
and displays a formatted receipt with: the data entered, the days late (clamped to zero with the ternary), the delay expressed in weeks and days, the fine with two decimals and a status label (ON TIME / OVERDUE) obtained with the ternary operator. Use System.err to emit a warning that the program does not validate its input. No if and no loops.
Solutions
Solution 1
Observed behaviour. The program shows the three questions, but only lets you type twice: the days and, afterwards, what will end up being the title. The output is something like:
Why. nextInt() consumes the characters 27 but leaves the \n from the Enter in the input stream. The first nextLine() finds that break immediately and returns an empty string without waiting, so employee ends up as "" and the name question flies past. The second nextLine() does wait, and whatever you type there (even if it is the name) ends up in title. The data is shifted by one position.
Fix 1: an extra nextLine().
import java.util.Scanner;
public class ScannerTestFixed1 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("Elapsed days: ");
int days = input.nextInt();
input.nextLine(); // <- KEY: consumes the pending \n left by nextInt()
System.out.print("Employee name: ");
String employee = input.nextLine();
System.out.print("Book title: ");
String title = input.nextLine();
System.out.println("Employee: [" + employee + "]");
System.out.println("Title: [" + title + "]");
System.out.println("Days: " + days);
input.close();
}
}Fix 2: everything with nextLine() (recommended).
import java.util.Scanner;
public class ScannerTestFixed2 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("Elapsed days: ");
// nextLine() consumes the COMPLETE line, break included.
// parseInt then turns that text into a number.
int days = Integer.parseInt(input.nextLine());
System.out.print("Employee name: ");
String employee = input.nextLine();
System.out.print("Book title: ");
String title = input.nextLine();
System.out.println("Employee: [" + employee + "]");
System.out.println("Title: [" + title + "]");
System.out.println("Days: " + days);
input.close();
}
}The second fix is preferable because it removes the cause instead of patching the symptom: if every read uses the same method, nothing is ever left pending in the stream.
Solution 2
public class CatalogTable {
public static void main(String[] args) {
// Column widths centralised in constants:
// changing the layout means changing these numbers.
final int TITLE_WIDTH = 22;
final int AUTHOR_WIDTH = 16;
final int YEAR_WIDTH = 5;
final int PRICE_WIDTH = 10;
// The separator line is built once and reused.
// The +2 accounts for the spaces surrounding each cell.
String separator = "+" + "-".repeat(TITLE_WIDTH + 2)
+ "+" + "-".repeat(AUTHOR_WIDTH + 2)
+ "+" + "-".repeat(YEAR_WIDTH + 2)
+ "+" + "-".repeat(PRICE_WIDTH + 2)
+ "+";
// Catalog data
double javaPrice = 45.90;
double patternsPrice = 52.00;
double refactoringPrice = 38.75;
double total = javaPrice + patternsPrice + refactoringPrice;
System.out.println(separator);
// Header: text on the left (-), numbers on the right (no -)
System.out.printf("| %-22s | %-16s | %5s | %10s |%n",
"TITLE", "AUTHOR", "YEAR", "PRICE");
System.out.println(separator);
// Data rows. %5d aligns the years, %10.2f aligns the amounts
// to the right with exactly two decimals.
System.out.printf("| %-22s | %-16s | %5d | %10.2f |%n",
"Effective Java", "Joshua Bloch", 2018, javaPrice);
System.out.printf("| %-22s | %-16s | %5d | %10.2f |%n",
"Design Patterns", "Erich Gamma", 1994, patternsPrice);
System.out.printf("| %-22s | %-16s | %5d | %10.2f |%n",
"Refactoring", "Martin Fowler", 1999, refactoringPrice);
System.out.println(separator);
// Total row: we visually merge the first two columns
// using a width equal to the sum of both plus the separator between them.
System.out.printf("| %-41s | %5s | %10.2f |%n", "TOTAL", "", total);
System.out.println(separator);
}
}Output:
+------------------------+------------------+-------+------------+ | TITLE | AUTHOR | YEAR | PRICE | +------------------------+------------------+-------+------------+ | Effective Java | Joshua Bloch | 2018 | 45.90 | | Design Patterns | Erich Gamma | 1994 | 52.00 | | Refactoring | Martin Fowler | 1999 | 38.75 | +------------------------+------------------+-------+------------+ | TOTAL | | 136.65 | +------------------------+------------------+-------+------------+
The detail to internalise is the 41 in the total row: it comes from 22 + 3 + 16 (the two widths plus the " | " that separated them). When you merge cells you have to add up the widths and the separators in between.
Solution 3
import java.util.Scanner;
public class ReturnRecord {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
// === BiblioTech business rules ===
final String COMPANY_NAME = "Nexus Software";
final int LOAN_DAYS = 15;
final int WIDTH = 48;
// === Header ===
System.out.println("=".repeat(WIDTH));
System.out.printf(" BiblioTech - %s%n", COMPANY_NAME);
System.out.println(" Return record");
System.out.println("=".repeat(WIDTH));
System.out.println();
// === Data collection (all with nextLine) ===
System.out.print("Employee : ");
String employee = input.nextLine();
System.out.print("Book title : ");
String title = input.nextLine();
System.out.print("ISBN : ");
String isbn = input.nextLine();
System.out.print("Elapsed days : ");
int elapsedDays = Integer.parseInt(input.nextLine());
System.out.print("Daily rate (EUR) : ");
double dailyRate = Double.parseDouble(input.nextLine());
// === Calculations ===
// The ternary clamps to zero: an early return
// can never produce a negative delay.
int daysLate = elapsedDays > LOAN_DAYS
? elapsedDays - LOAN_DAYS
: 0;
int weeks = daysLate / 7; // integer quotient
int leftoverDays = daysLate % 7; // remainder
double fine = daysLate * dailyRate; // int * double -> double
String status = daysLate > 0 ? "OVERDUE" : "ON TIME";
// === Receipt ===
System.out.println();
System.out.println("=".repeat(WIDTH));
System.out.println(" RETURN RECEIPT");
System.out.println("=".repeat(WIDTH));
System.out.printf("%-22s %s%n", "Employee:", employee);
System.out.printf("%-22s %s%n", "Book:", title);
System.out.printf("%-22s %s%n", "ISBN:", isbn);
System.out.println("-".repeat(WIDTH));
System.out.printf("%-22s %5d days%n", "Standard loan:", LOAN_DAYS);
System.out.printf("%-22s %5d days%n", "Elapsed:", elapsedDays);
System.out.printf("%-22s %5d days (%d wk. and %d days)%n",
"Late by:", daysLate, weeks, leftoverDays);
System.out.println("-".repeat(WIDTH));
System.out.printf("%-22s %8.2f EUR%n", "Daily rate:", dailyRate);
System.out.printf("%-22s %8.2f EUR%n", "FINE TO PAY:", fine);
System.out.printf("%-22s %s%n", "Status:", status);
System.out.println("=".repeat(WIDTH));
// Warning on the error channel: it is not part of the receipt.
System.err.println("[warning] This program does not validate the input entered.");
input.close();
}
}A sample run:
================================================ BiblioTech - Nexus Software Return record ================================================ Employee : Nuria Vidal Book title : Design Patterns ISBN : 978-0000000002 Elapsed days : 34 Daily rate (EUR) : 0.25 ================================================ RETURN RECEIPT ================================================ Employee: Nuria Vidal Book: Design Patterns ISBN: 978-0000000002 ------------------------------------------------ Standard loan: 15 days Elapsed: 34 days Late by: 19 days (2 wk. and 5 days) ------------------------------------------------ Daily rate: 0.25 EUR FINE TO PAY: 4.75 EUR Status: OVERDUE ================================================
Conclusion
Your programs now communicate in both directions. On the output side, you have a grip on print, println and above all printf with its specifiers (%s, %d, %.2f, %n), its widths and its alignment, which lets you build cards and tables that look professional; you know String.format for storing that text instead of printing it, and you know that System.err is a separate channel for warnings and errors. On the input side, you know how to create a Scanner over System.in, when to use nextLine versus next, why mixing nextInt() with nextLine() breaks the flow and how to avoid it by reading everything as a line and converting with Integer.parseInt and Double.parseDouble, and why the Scanner must be created once and closed at the end.
With this you have all the module's pieces: syntax, variables, types, operators and input/output. In the next lesson, Your First Complete Program: BiblioTech, you will assemble them all together in BiblioTechApp: a program that welcomes the user, collects the details of a loan, calculates the delay and the fine, and prints a formatted receipt. We will build it in successive versions, you will compile and run it, check its output with two sets of test data and honestly analyse what it still cannot do and which module will solve each gap.
Java Programming Course
Module 1: Introduction to Java
- Introduction to Java
- Setting Up the Development Environment
- Basic Syntax and Structure
- Variables and Data Types
- Operators
- Console Input and Output
- Your First Complete Program: BiblioTech
Module 2: Control Flow
- Conditional Statements
- Loops
- Switch Statements
- Break and Continue
- Debugging and Execution Traces
- Project: The BiblioTech Interactive Menu
Module 3: Object-Oriented Programming
- Introduction to OOP
- Classes and Objects
- Methods
- Constructors
- Inheritance
- Polymorphism
- Encapsulation
- Abstraction
- The Object Class: equals, hashCode and toString
Module 4: Advanced Object-Oriented Programming
- Interfaces
- Abstract Classes
- Inner Classes
- Anonymous Classes
- Lambda Expressions
- Functional Interfaces and Method References
- Enums and Records
Module 5: Data Structures and Collections
- Arrays
- The Collections Framework
- ArrayList
- LinkedList
- HashMap
- HashSet
- Queue and Deque
- Stack
- Sorting and Searching Collections
Module 6: Exception Handling
- Introduction to Exceptions
- The Try-Catch Block
- Throw and Throws
- Custom Exceptions
- The Finally Block
- Try-with-resources and AutoCloseable
- Error Handling Strategies and Logging
Module 7: File Input/Output
- Reading Files
- Writing Files
- File Streams
- BufferedReader and BufferedWriter
- Serialization
- The NIO.2 API: Path and Files
- Interchange Formats: CSV and Properties
Module 8: Multithreading and Concurrency
- Introduction to Multithreading
- Creating Threads
- Thread Lifecycle
- Synchronization
- Concurrency Utilities
- Concurrent Collections and Atomic Variables
- Asynchronous Tasks with CompletableFuture
Module 9: Networking
- Introduction to Networking
- Sockets
- ServerSocket
- DatagramSocket and DatagramPacket
- URL and HttpURLConnection
- The Modern HTTP Client
Module 10: Advanced Topics
- Generics
- Annotations
- Reflection
- Java 8 Features: Streams and Optional
- Dates and Times with java.time
- Java 9 and Beyond
- Memory, Garbage Collection and Performance
Module 11: Java Frameworks and Libraries
- Introduction to Java Frameworks
- Spring Framework
- Hibernate
- JUnit
- Maven
- Advanced Testing with Mockito
- Essential Ecosystem Libraries
