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

  1. Standard output: System.out
  2. print versus println
  3. printf and the format specifiers
  4. Widths, alignment and other modifiers
  5. String.format: the same formatting, in a variable
  6. System.err and when to use it
  7. Reading data with Scanner
  8. The reading methods and what each one returns
  9. The classic problem: mixing nextInt() with nextLine()
  10. Closing the Scanner
  11. Converting text to numbers: parseInt and parseDouble
  12. The BiblioTech book card
  13. Common Mistakes and Tips
  14. Exercises

  1. Standard output: System.out

System.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:

System.out.println("BiblioTech");
  • System is a class in java.lang that gives access to system resources.
  • out is a field of that class: a PrintStream object connected to the console.
  • println is 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

  1. print versus println

The 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:

Title: Effective Java
ISBN: 978-0000000001

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');             // J

And 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.

  1. printf and the format specifiers

printf (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:

The book Effective Java has accrued a fine of 13.88 EUR

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:

  • %f shows SIX decimals by default. printf("%f", 3.5) prints 3.500000. You will almost always want %.2f.
  • Use %n, not \n. %n produces the line break native to the operating system (\n on Unix, \r\n on Windows); \n always produces the Unix one. On the console you barely notice, but it is the correct practice.
  • printf does 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 dot

It 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:

Marta Ruiz has Effective Java 12 days overdue: 3.00 EUR

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.

  1. 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:

%[flags][width][.precision]conversion
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.

  1. String.format: the same formatting, in a variable

Sometimes 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.

  1. System.err and when to use it

Alongside 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.log

This 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.

  1. Reading data with Scanner

Scanner 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:

Scanner input = new Scanner(System.in);
  • new Scanner(...) creates a new object. new is the object creation operator; you will study it in depth in module 3, but you already need it here.
  • System.in is standard input: the keyboard, the counterpart of System.out for output.
  • input is the name we give it. It could be sc, keyboard or reader; 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.

  1. 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 pending

Practical 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.

  1. The classic problem: mixing 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:

2  7  \n

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 wait

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

A 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.

  1. Closing the Scanner

Scanner is a system resource and must be closed when it is no longer needed:

input.close();

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.

  1. Converting text to numbers: parseInt and parseDouble

Everything 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 number

That 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 addition

What happens if the text is not a number

int days = Integer.parseInt("twenty-seven");

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:

  1. You know what that error means if it shows up.
  2. 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.

  1. 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:

  1. A single Scanner, created at the start and closed at the end.
  2. All reading with nextLine(), with explicit conversion. It is never mixed with nextInt().
  3. System.out.print for the questions, so that the cursor stays on the line.
  4. %-20s on the labels, which creates a perfectly aligned left column.
  5. %8.2f for 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 produces cannot find symbol: class Scanner. It is the import you will forget most often in the whole course.
  • Mixing nextInt() with nextLine(). The program skips questions. Use nextLine() for everything and convert with parseInt.
  • Using next() to read titles or names. It cuts off at the first space: "Marta Ruiz" becomes "Marta".
  • Using %d for a double. It throws IllegalFormatConversionException. %d is only for integers; for decimals, %f.
  • %f showing six decimals. That is its default behaviour; always specify the precision: %.2f.
  • Forgetting the %n at the end of a printf. All the lines stick together.
  • Creating a new Scanner after closing another one over System.in. It does not work: System.in is closed. One single Scanner per program.
  • Entering 0,25 instead of 0.25. Double.parseDouble expects the dot and throws NumberFormatException with 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:

Elapsed days: 27
Employee name: Book title: Marta Ruiz
Employee: []
Title:    [Marta Ruiz]
Days:     27

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

Module 2: Control Flow

Module 3: Object-Oriented Programming

Module 4: Advanced Object-Oriented Programming

Module 5: Data Structures and Collections

Module 6: Exception Handling

Module 7: File Input/Output

Module 8: Multithreading and Concurrency

Module 9: Networking

Module 10: Advanced Topics

Module 11: Java Frameworks and Libraries

Module 12: Building Real-World Applications

© Copyright 2026. All rights reserved