The previous six lessons have given you separate pieces: the structure of a Java file, variables and their types, operators and console input/output. This lesson assembles them all into the first real program of the course: BiblioTechApp, the application Nexus Software will use to register the return of a book from its technical library and print the receipt with the corresponding fine. We will not write it in one go: we will build it in four successive versions, each improving on the previous one, which is exactly how software is developed in real life. At the end we will honestly analyse what it still cannot do and which module of the course will solve each shortcoming.

Contents

  1. The program specification
  2. Version 1: the skeleton with fixed data
  3. Version 2: asking the user for the data
  4. Version 3: correct calculations with the ternary operator
  5. Version 4: formatted output and the fine cap
  6. The complete final code, commented
  7. Compiling and running BiblioTechApp
  8. Test data sets and expected output
  9. The program's limitations and how they will be solved
  10. Common Mistakes and Tips
  11. Exercises

  1. The program specification

Before writing a single line, it is worth pinning down what the program has to do. This is the first thing done in any professional project, and it saves an enormous amount of work.

Name: BiblioTechApp Package: com.nexussoftware.bibliotech

Expected behaviour:

  1. Show a welcome header with the company and application names.
  2. Ask on the console for the details of a loan return:
    • Employee name
    • Book title
    • ISBN
    • Days elapsed since it was lent
  3. Calculate:
    • The days late, which can never be negative.
    • The delay expressed in whole weeks and leftover days.
    • The fine: days late multiplied by the daily rate.
    • The cap: the fine can never exceed the established maximum.
    • The loan status: on time or overdue.
  4. Show a formatted, readable receipt with all that data.

Business rules of the Nexus Software library:

Rule Value Constant
Standard loan duration 15 days LOAN_DAYS
Rate per day late €0.25 DAILY_RATE
Maximum fine per loan €20.00 MAX_FINE
Minor delay threshold 7 days MINOR_THRESHOLD

Technical constraints of this version: a single main method, no conditionals, no loops, no classes of your own. Everything you need you have already learnt.

flowchart TD
    A["Show header"] --> B["Ask for employee, title,<br/>ISBN and elapsed days"]
    B --> C["Calculate days late<br/>(clamped to zero)"]
    C --> D["Break down into<br/>weeks and days"]
    D --> E["Calculate base fine<br/>= days late x rate"]
    E --> F["Apply the<br/>MAX_FINE cap"]
    F --> G["Determine status<br/>and severity"]
    G --> H["Print formatted<br/>receipt with printf"]

  1. Version 1: the skeleton with fixed data

The first version asks for nothing: the data is written into the code. It serves to validate the structure and the calculations without the complication of input.

package com.nexussoftware.bibliotech;

public class BiblioTechApp {

    public static void main(String[] args) {

        final String COMPANY_NAME = "Nexus Software";
        final int LOAN_DAYS = 15;
        final double DAILY_RATE = 0.25;

        String employee = "Marta Ruiz";
        String title = "Effective Java";
        String isbn = "978-0000000001";
        int elapsedDays = 27;

        int daysLate = elapsedDays - LOAN_DAYS;
        double fine = daysLate * DAILY_RATE;

        System.out.println("=== BiblioTech - " + COMPANY_NAME + " ===");
        System.out.println("Employee: " + employee);
        System.out.println("Book: " + title);
        System.out.println("ISBN: " + isbn);
        System.out.println("Days late: " + daysLate);
        System.out.println("Fine: " + fine + " EUR");
    }
}

Output:

=== BiblioTech - Nexus Software ===
Employee: Marta Ruiz
Book: Effective Java
ISBN: 978-0000000001
Days late: 12
Fine: 3.0 EUR

It works, but it has three obvious flaws that we will fix one by one:

  1. The data is fixed. To process another return you have to edit the code and recompile.
  2. The calculations are naive. If the book is returned after 9 days, daysLate would be -6 and the fine -1.5 EUR: the library paying the employee.
  3. The output is poor. 3.0 EUR is not a presentable amount, and the labels are not aligned.

  1. Version 2: asking the user for the data

We replace the fixed values with Scanner reading, applying the rule from the previous lesson: everything is read with nextLine() and converted with Integer.parseInt where needed.

package com.nexussoftware.bibliotech;

import java.util.Scanner;

public class BiblioTechApp {

    public static void main(String[] args) {

        Scanner input = new Scanner(System.in);

        final String COMPANY_NAME = "Nexus Software";
        final int LOAN_DAYS = 15;
        final double DAILY_RATE = 0.25;

        System.out.println("=== BiblioTech - " + COMPANY_NAME + " ===");
        System.out.println("Loan return record");
        System.out.println();

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

        int daysLate = elapsedDays - LOAN_DAYS;
        double fine = daysLate * DAILY_RATE;

        System.out.println();
        System.out.println("Employee: " + employee);
        System.out.println("Book: " + title);
        System.out.println("ISBN: " + isbn);
        System.out.println("Days late: " + daysLate);
        System.out.println("Fine: " + fine + " EUR");

        input.close();
    }
}

It is already a useful program: the same binary serves any return. But the negative-numbers flaw is still there. Try it by entering 9 for the elapsed days and you will see Fine: -1.5 EUR.

  1. Version 3: correct calculations with the ternary operator

This is where the ternary operator comes in to clamp the delay to zero, and while we are at it we add the breakdown into weeks and the loan status. This is the fragment that changes:

// The delay can never be negative: if it is returned early, it is 0.
int daysLate = elapsedDays > LOAN_DAYS
        ? elapsedDays - LOAN_DAYS
        : 0;

// Breaking down the delay: / gives the whole weeks, % the leftover days.
int weeksLate = daysLate / 7;
int leftoverDays = daysLate % 7;

// Days of term remaining (0 if it has already expired).
int daysRemaining = elapsedDays < LOAN_DAYS
        ? LOAN_DAYS - elapsedDays
        : 0;

double baseFine = daysLate * DAILY_RATE;

// Text labels chosen with ternaries.
String status = daysLate > 0 ? "OVERDUE" : "ON TIME";
String severity = daysLate == 0 ? "-"
                : daysLate <= MINOR_THRESHOLD ? "MINOR"
                : "SEVERE";

This block concentrates a good part of what was learnt in the operators lesson: comparison, subtraction, integer division, modulo, multiplication with automatic promotion from int to double, a simple ternary and a nested ternary. None of this requires if.

  1. Version 4: formatted output and the fine cap

Two things are missing: applying the fine cap and presenting the receipt as a document. Both are solved with what we saw in lesson 01-06.

// Cap: if the base fine exceeds the maximum, the maximum is charged.
double finalFine = baseFine > MAX_FINE ? MAX_FINE : baseFine;
String capApplied = baseFine > MAX_FINE ? "YES" : "NO";

// Formatted receipt: labels left-aligned in width 22,
// amounts right-aligned with exactly two decimals.
System.out.println("=".repeat(WIDTH));
System.out.printf("%-22s %s%n", "Employee:", employee);
System.out.printf("%-22s %8.2f EUR%n", "FINE TO PAY:", finalFine);

With this, 3.0 becomes 3.00 and the columns line up. We now have the complete program.

  1. The complete final code, commented

package com.nexussoftware.bibliotech;

import java.util.Scanner;

/**
 * BiblioTechApp - Management system for the internal technical library
 * of Nexus Software.
 *
 * Version 0.1 from module 1: linear record of a loan return, with
 * delay and fine calculation, and printing of a receipt.
 *
 * @author BiblioTech Team - Nexus Software
 * @version 0.1
 */
public class BiblioTechApp {

    public static void main(String[] args) {

        // ---------------------------------------------------------------
        // 1. RESOURCES
        // ---------------------------------------------------------------
        // A single Scanner for the whole application, connected to the
        // keyboard. Closing a Scanner over System.in closes System.in, so
        // we never create more than one.
        Scanner input = new Scanner(System.in);

        // ---------------------------------------------------------------
        // 2. BUSINESS RULES
        // ---------------------------------------------------------------
        // All the constants go together at the top: changing a rate means
        // editing ONE line, not hunting for the number all over the
        // program. 'final' guarantees nobody changes them by accident.
        final String COMPANY_NAME = "Nexus Software";
        final String VERSION = "0.1";
        final int LOAN_DAYS = 15;            // standard loan duration
        final double DAILY_RATE = 0.25;      // euros per day late
        final double MAX_FINE = 20.0;        // absolute cap on the fine
        final int MINOR_THRESHOLD = 7;       // up to 7 days, the delay is minor
        final int DAYS_PER_WEEK = 7;
        final int WIDTH = 52;                // width of the receipt lines

        // ---------------------------------------------------------------
        // 3. WELCOME HEADER
        // ---------------------------------------------------------------
        // "=".repeat(WIDTH) builds the separator line without typing
        // 52 equals signs by hand. repeat() exists since Java 11.
        System.out.println("=".repeat(WIDTH));
        System.out.printf("  BiblioTech v%s - %s%n", VERSION, COMPANY_NAME);
        System.out.println("  Loan return record");
        System.out.println("=".repeat(WIDTH));
        System.out.println();

        // ---------------------------------------------------------------
        // 4. DATA COLLECTION
        // ---------------------------------------------------------------
        // We use print (without ln) so that the cursor stays on the same
        // line as the question.
        // ALL reading is done with nextLine(): that way no line break is
        // ever left pending in the input stream, which is what causes the
        // classic nextInt()-followed-by-nextLine() problem.
        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       : ");
        // The text read is converted to a number with parseInt.
        // If the user types something that is not an integer, the program
        // ends with NumberFormatException (handled in module 6).
        int elapsedDays = Integer.parseInt(input.nextLine());

        // ---------------------------------------------------------------
        // 5. CALCULATIONS
        // ---------------------------------------------------------------

        // Days late clamped to zero. Without the ternary, an early return
        // would give a negative delay and therefore a negative fine:
        // the library paying the employee.
        int daysLate = elapsedDays > LOAN_DAYS
                ? elapsedDays - LOAN_DAYS
                : 0;

        // Days of term remaining. It only makes sense if it has not expired.
        int daysRemaining = elapsedDays < LOAN_DAYS
                ? LOAN_DAYS - elapsedDays
                : 0;

        // Breaking down the delay: integer division gives the whole weeks
        // and the modulo gives the leftover days.
        int weeksLate = daysLate / DAYS_PER_WEEK;
        int leftoverDays = daysLate % DAYS_PER_WEEK;

        // Base fine. daysLate is an int and DAILY_RATE is a double:
        // Java promotes the int to a double before multiplying.
        double baseFine = daysLate * DAILY_RATE;

        // Fine cap. The amount charged never exceeds MAX_FINE.
        double finalFine = baseFine > MAX_FINE ? MAX_FINE : baseFine;
        String capApplied = baseFine > MAX_FINE ? "YES" : "NO";

        // Text labels derived from the calculations.
        String status = daysLate > 0 ? "OVERDUE" : "ON TIME";

        // Nested ternary, read as a ladder from top to bottom.
        String severity = daysLate == 0 ? "-"
                        : daysLate <= MINOR_THRESHOLD ? "MINOR"
                        : "SEVERE";

        // Receipt reference code: the employee's initial in uppercase
        // + the last four digits of the ISBN.
        // charAt(0) takes the first character; substring cuts from the
        // given position to the end.
        String reference = String.format("%c%s-%s",
                employee.charAt(0),
                title.substring(0, 3).toUpperCase(),
                isbn.substring(isbn.length() - 4));

        // ---------------------------------------------------------------
        // 6. RECEIPT
        // ---------------------------------------------------------------
        System.out.println();
        System.out.println("=".repeat(WIDTH));
        System.out.println("  RETURN RECEIPT");
        System.out.println("=".repeat(WIDTH));

        // %-22s aligns the label to the LEFT in width 22, creating a
        // uniform column. %s sticks the value right after it.
        System.out.printf("%-22s %s%n", "Reference:", reference);
        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));

        // %5d aligns the integers to the RIGHT in width 5, so that the
        // units land in the same column and can be compared at a glance.
        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%n", "Days remaining:", daysRemaining);
        System.out.printf("%-22s %5d days (%d wk. and %d days)%n",
                "Late by:", daysLate, weeksLate, leftoverDays);

        System.out.println("-".repeat(WIDTH));

        // %8.2f: total width 8, exactly 2 decimals. That is what turns a
        // raw 3.0 into a presentable 3.00.
        System.out.printf("%-22s %8.2f EUR%n", "Daily rate:", DAILY_RATE);
        System.out.printf("%-22s %8.2f EUR%n", "Fine calculated:", baseFine);
        System.out.printf("%-22s %8.2f EUR%n", "Maximum fine:", MAX_FINE);
        System.out.printf("%-22s %8s%n", "Cap applied:", capApplied);

        System.out.println("-".repeat(WIDTH));

        System.out.printf("%-22s %8.2f EUR%n", "TOTAL TO PAY:", finalFine);
        System.out.printf("%-22s %s (%s)%n", "Status:", status, severity);

        System.out.println("=".repeat(WIDTH));

        // Warning on the error channel: it is not part of the receipt and
        // can be redirected separately when running the program.
        System.err.println("[warning] Version 0.1: the program does not validate input.");

        // ---------------------------------------------------------------
        // 7. RELEASING RESOURCES
        // ---------------------------------------------------------------
        input.close();
    }
}

  1. Compiling and running BiblioTechApp

The file must be on the path corresponding to its package:

bibliotech/
└── src/
    └── main/
        └── java/
            └── com/
                └── nexussoftware/
                    └── bibliotech/
                        └── BiblioTechApp.java

Standing in src/main/java:

# Compile. You give the PATH of the file, with slashes.
# -d sends the .class files to a separate 'out' folder, away from the source.
javac -d ../../../out com/nexussoftware/bibliotech/BiblioTechApp.java

# Run. You give the qualified CLASS NAME, with dots.
# -cp tells the java command where to look for the compiled classes.
java -cp ../../../out com.nexussoftware.bibliotech.BiblioTechApp

If you prefer to work without a package for a quick test, remove the first line (package ...;) and use the Java 11+ single-file mode:

java BiblioTechApp.java

And if redirecting the output is useful to you, remember that the receipt and the warning travel on different channels:

# The receipt is saved to a file; the warning still appears on screen
java -cp ../../../out com.nexussoftware.bibliotech.BiblioTechApp > receipt.txt

In the IDE, just press the run button on the class; IntelliJ and Eclipse run exactly these two commands underneath.

  1. Test data sets and expected output

Testing a program means running it with data chosen deliberately to cover different situations. These are the two cases you should verify:

Data Set A: overdue loan Set B: on-time loan
Employee Marta Ruiz Diego Alonso
Title Effective Java Refactoring
ISBN 978-0000000001 978-0000000003
Elapsed days 27 9
Expected days late 12 0
Expected days remaining 0 6
Breakdown 1 week and 5 days 0 weeks and 0 days
Fine calculated 3.00 EUR 0.00 EUR
Cap applied NO NO
Total to pay 3.00 EUR 0.00 EUR
Status OVERDUE (SEVERE) ON TIME (-)
Reference MEFF-0001 DREF-0003

Complete output for set A

====================================================
  BiblioTech v0.1 - Nexus Software
  Loan return record
====================================================

Employee           : Marta Ruiz
Book title         : Effective Java
ISBN               : 978-0000000001
Elapsed days       : 27

====================================================
  RETURN RECEIPT
====================================================
Reference:             MEFF-0001
Employee:              Marta Ruiz
Book:                  Effective Java
ISBN:                  978-0000000001
----------------------------------------------------
Standard loan:            15 days
Elapsed:                  27 days
Days remaining:            0 days
Late by:                  12 days (1 wk. and 5 days)
----------------------------------------------------
Daily rate:                0.25 EUR
Fine calculated:           3.00 EUR
Maximum fine:             20.00 EUR
Cap applied:                    NO
----------------------------------------------------
TOTAL TO PAY:              3.00 EUR
Status:                OVERDUE (SEVERE)
====================================================

Complete output for set B

====================================================
  BiblioTech v0.1 - Nexus Software
  Loan return record
====================================================

Employee           : Diego Alonso
Book title         : Refactoring
ISBN               : 978-0000000003
Elapsed days       : 9

====================================================
  RETURN RECEIPT
====================================================
Reference:             DREF-0003
Employee:              Diego Alonso
Book:                  Refactoring
ISBN:                  978-0000000003
----------------------------------------------------
Standard loan:            15 days
Elapsed:                   9 days
Days remaining:            6 days
Late by:                   0 days (0 wk. and 0 days)
----------------------------------------------------
Daily rate:                0.25 EUR
Fine calculated:           0.00 EUR
Maximum fine:             20.00 EUR
Cap applied:                    NO
----------------------------------------------------
TOTAL TO PAY:              0.00 EUR
Status:                ON TIME (-)
====================================================

Note the value of set B: the "on time" case is the one that proves the ternary works. Without it, the delay would be -6 and the fine -1.50 EUR. Choosing data that exercises the edge cases, and not just the normal case, is the heart of software testing (module 11).

A third case worth trying on your own: Nuria Vidal, Design Patterns, ISBN 978-0000000002, 120 days. The delay would be 105 days and the calculated fine 26.25 EUR, above the maximum: the program should show Cap applied: YES and a total of 20.00 EUR.

  1. The program's limitations and how they will be solved

BiblioTechApp works, but it is a toy program and it is worth being explicit about why. Recognising the shortcomings of your own code is one of the most valuable skills a developer can have.

Limitation What happens today When it is solved
It does not validate input If you type twenty-seven for the days, the program crashes with NumberFormatException Module 2 (checking with hasNextInt) and module 6 (catching the exception and reacting)
It does not repeat It processes one return and ends. For the next one you have to run it again Module 2: loops and an interactive menu (lesson 02-06)
It does not make complex decisions Everything is resolved with ternaries, which only choose values, they do not run different actions Module 2: if, switch
The data is scattered title, author, isbn and available are loose variables with no relationship between them Module 3: the Book class will group them into a type of their own
It only handles one book and one employee There is no way to manage a catalog of hundreds of titles Module 5: ArrayList and HashMap
It saves nothing When you close the program, the receipt disappears forever Module 7: writing files, CSV
It notifies nobody Nobody receives a reminder before the term expires Module 8: background tasks
It only works on your machine A colleague cannot check the catalog from their own computer Modules 9 and 12: networking and a web application
It uses double for money The amounts can accumulate rounding errors Module 10: BigDecimal
There are no tests You check the output by eye, running it by hand Module 11: JUnit

Notice the logic of the course: each later module removes one concrete, visible limitation of the program you have just written. You will not learn loops because it is the next topic, but because today your program only processes one return. You will not learn classes just because, but because today title and author are two variables with nothing binding them together.

Common Mistakes and Tips

  • Forgetting the extra input.nextLine() after a nextInt(). It does not happen in this program because all reading uses nextLine(), but if you mix methods it will come back.
  • Typing 0,25 instead of 0.25 when entering decimals. Double.parseDouble expects the dot, whatever separator printf displays.
  • substring(isbn.length() - 4) failing. If the ISBN has fewer than 4 characters, it throws StringIndexOutOfBoundsException. In this version we assume correct input.
  • employee.charAt(0) failing if the user presses Enter without typing anything: an empty string has no character at position 0.
  • Using %d with a double in the total's printf. It causes IllegalFormatConversionException.
  • Forgetting %n in one of the printfs, which glues two receipt lines together.
  • Scattering the constants through the code. Group them at the top: it is the difference between changing the rate in ten seconds and hunting for 0.25 across the whole file.
  • Tip: when a calculation has several steps (base fine → cap → total), create one variable per step with a descriptive name. A single giant expression is impossible to debug.
  • Tip: run the program with all three data sets (normal delay, on time, cap exceeded) every time you modify it. It is your first manual test suite, and in module 11 you will turn it into an automated one.
  • Tip: keep this version of BiblioTechApp.java. In module 2 you will start from it to add the interactive menu, and comparing the two versions will show you your own progress.

Exercises

Exercise 1: Extending the receipt with the deposit and the due notice

Modify BiblioTechApp to add to the receipt:

  1. A deposit of 10 % of the book's price (ask for it on the console as a double), which is refunded in full if there is no delay and is reduced by the amount of the fine if there is. The deposit to refund can never be negative.
  2. A due-soon notice: a piece of text valued "DUE SOON" if there are 3 days or fewer of term left (and it has not expired yet), "IN PROGRESS" if there are more, and "OVERDUE" if there is already a delay.

Use only ternaries, no if. Format every amount with two decimals.

Exercise 2: Loan percentages and statistics

Extend the receipt with three calculated indicators, all with two decimals:

  1. The percentage of the term consumed: elapsedDays / LOAN_DAYS * 100. Careful with integer division.
  2. The average cost per elapsed day: the final fine divided by the elapsed days.
  3. The percentage of the maximum fine represented by the final fine.

Show all three aligned in an "INDICATORS" section of the receipt.

Exercise 3: A receipt for two simultaneous loans

Without using loops or arrays, extend the program so that it registers the return of two books from the same employee and shows a combined receipt with:

  • The details of both books in a table aligned with printf.
  • The individual fine for each one.
  • The accumulated total, subject to a joint MAX_FINE cap.
  • Which of the two books has accrued the greater delay (with the ternary operator).

Reflect at the end: what would happen if Nexus Software wanted to process ten books? Write down your answer; we will come back to it in module 5.

Solutions

Solution 1

Add the reading of the price and this block of calculations and output (the rest of the program does not change):

        // --- Additional reading ---
        System.out.print("Book price         : ");
        double bookPrice = Double.parseDouble(input.nextLine());

        // --- Deposit and notice calculations ---
        final double DEPOSIT_PERCENTAGE = 0.10;  // 10 % of the price
        final int NOTICE_THRESHOLD = 3;          // days before warning of expiry

        // The deposit is 10 % of the price: double * double -> double.
        double deposit = bookPrice * DEPOSIT_PERCENTAGE;

        // The fine is deducted, but it can never end up negative:
        // the ternary clamps the result to zero.
        double depositReturned = deposit - finalFine > 0
                ? deposit - finalFine
                : 0.0;

        // The withheld amount is the part of the deposit the library keeps.
        double depositWithheld = deposit - depositReturned;

        // Due notice: a ladder of ternaries.
        // It is evaluated top to bottom and stops at the first true condition.
        String notice = daysLate > 0 ? "OVERDUE"
                      : daysRemaining <= NOTICE_THRESHOLD ? "DUE SOON"
                      : "IN PROGRESS";

        // --- Additional output ---
        System.out.println("-".repeat(WIDTH));
        System.out.printf("%-22s %8.2f EUR%n", "Book price:", bookPrice);
        System.out.printf("%-22s %8.2f EUR%n", "Deposit paid:", deposit);
        System.out.printf("%-22s %8.2f EUR%n", "Deposit withheld:", depositWithheld);
        System.out.printf("%-22s %8.2f EUR%n", "DEPOSIT TO REFUND:", depositReturned);
        System.out.printf("%-22s %s%n", "Notice:", notice);

Checking with the three cases:

Case Price Deposit (10 %) Fine Withheld To refund Notice
Marta, 27 days 45.90 4.59 3.00 3.00 1.59 OVERDUE
Diego, 9 days 38.75 3.88 0.00 0.00 3.88 IN PROGRESS
Diego, 13 days 38.75 3.88 0.00 0.00 3.88 DUE SOON

The key point of the exercise is the order of the ternary ladder: the daysLate > 0 check must come first. If it were placed later, an expired loan with daysRemaining equal to 0 would fall into the "DUE SOON" branch, which would be wrong.

Solution 2

        // --- Indicators ---
        // Careful: elapsedDays and LOAN_DAYS are ints, so the division
        // would be an INTEGER one. We have to force a double first.
        double termPercentage = (double) elapsedDays / LOAN_DAYS * 100;

        // Average cost per elapsed day. Here finalFine is already a double,
        // so the promotion is automatic and no cast is needed.
        double averageDailyCost = finalFine / elapsedDays;

        // What percentage of the cap the amount charged represents.
        double maxFinePercentage = finalFine / MAX_FINE * 100;

        System.out.println("-".repeat(WIDTH));
        System.out.println("  INDICATORS");
        System.out.println("-".repeat(WIDTH));
        System.out.printf("%-22s %8.2f %%%n", "Term consumed:", termPercentage);
        System.out.printf("%-22s %8.4f EUR/day%n", "Average daily cost:", averageDailyCost);
        System.out.printf("%-22s %8.2f %%%n", "Of maximum fine:", maxFinePercentage);

Output for set A (Marta Ruiz, 27 days, fine 3.00):

----------------------------------------------------
  INDICATORS
----------------------------------------------------
Term consumed:           180.00 %
Average daily cost:      0.1111 EUR/day
Of maximum fine:          15.00 %

Three important details:

  • The (double) cast is mandatory in the first calculation. Without it, 27 / 15 would give 1 (integer division) and the percentage would be 100.00 % instead of 180.00 %. It is the trap from lesson 01-05 in a real case.
  • %% prints a literal percent sign. A lone % would make printf try to read it as the start of a specifier and throw UnknownFormatConversionException.
  • %.4f for the daily cost because with two decimals the value 0.1111 would round to 0.11 and lose its meaning. The precision must be chosen according to the magnitude of the data.

Solution 3

        // ================= SECOND BOOK =================
        System.out.println();
        System.out.println("--- Second book ---");

        System.out.print("Book title 2       : ");
        String title2 = input.nextLine();

        System.out.print("ISBN 2             : ");
        String isbn2 = input.nextLine();

        System.out.print("Elapsed days 2     : ");
        int elapsedDays2 = Integer.parseInt(input.nextLine());

        // The same calculations as for the first one, duplicated.
        // This duplication is precisely what METHODS (module 3) and
        // LOOPS over collections (module 5) will solve.
        int daysLate2 = elapsedDays2 > LOAN_DAYS
                ? elapsedDays2 - LOAN_DAYS
                : 0;

        double baseFine2 = daysLate2 * DAILY_RATE;

        // Combined total, subject to a single cap for the whole return.
        double grossTotal = baseFine + baseFine2;
        double finalTotal = grossTotal > MAX_FINE ? MAX_FINE : grossTotal;

        // Which one has accrued the greater delay.
        String mostOverdueBook = daysLate >= daysLate2 ? title : title2;
        int highestDelay = daysLate >= daysLate2 ? daysLate : daysLate2;

        // ================= COMBINED RECEIPT =================
        System.out.println();
        System.out.println("=".repeat(WIDTH));
        System.out.println("  COMBINED RETURN RECEIPT");
        System.out.println("=".repeat(WIDTH));
        System.out.printf("%-22s %s%n", "Employee:", employee);
        System.out.println("-".repeat(WIDTH));

        // Table header and two rows with the same format.
        System.out.printf("%-24s %-16s %5s %9s%n", "BOOK", "ISBN", "LATE", "FINE");
        System.out.printf("%-24s %-16s %5d %9.2f%n", title, isbn, daysLate, baseFine);
        System.out.printf("%-24s %-16s %5d %9.2f%n", title2, isbn2, daysLate2, baseFine2);

        System.out.println("-".repeat(WIDTH));
        System.out.printf("%-22s %8.2f EUR%n", "Sum of fines:", grossTotal);
        System.out.printf("%-22s %8.2f EUR%n", "TOTAL TO PAY:", finalTotal);
        System.out.printf("%-22s %s (%d days)%n", "Highest delay:", mostOverdueBook, highestDelay);
        System.out.println("=".repeat(WIDTH));

Sample output with Marta Ruiz, "Effective Java" (27 days) and "Design Patterns" (40 days):

====================================================
  COMBINED RETURN RECEIPT
====================================================
Employee:              Marta Ruiz
----------------------------------------------------
BOOK                     ISBN              LATE      FINE
Effective Java           978-0000000001      12      3.00
Design Patterns          978-0000000002      25      6.25
----------------------------------------------------
Sum of fines:               9.25 EUR
TOTAL TO PAY:               9.25 EUR
Highest delay:         Design Patterns (25 days)
====================================================

The final reflection in this exercise is the most important of the whole module. For ten books you would need title1title10, isbn1isbn10, daysLate1daysLate10: thirty variables, thirty identical reading blocks and thirty copied calculations. The code would be three times longer, impossible to maintain, and typing daysLate3 where you meant daysLate4 would be enough to produce a wrong receipt that nobody would notice.

That pain has three solutions and you will see them in this order: loops (module 2) remove the repetition of the process; classes (module 3) group title, isbn and daysLate into a single Book object; and collections (module 5) store as many books as needed in a single variable. It is no coincidence that the course follows exactly that path.

Conclusion

You have written your first complete, working Java program. BiblioTechApp welcomes the user, collects the details of a return on the console, calculates the days late clamped to zero with the ternary operator, breaks them down into weeks and days with integer division and the modulo, obtains the fine by automatically promoting int to double, applies a cap to it, derives status and severity labels with nested ternaries, composes a reference with String methods and prints a perfectly aligned receipt with printf. All of it with a single main, well-grouped final constants and a single, properly closed Scanner.

With this lesson you close module 1. You know what Java is and how it runs, you have a working development environment, you understand the anatomy of a source file, you handle variables, types, literals and conversions, you have a grip on the operators and their traps, and you know how to communicate with the user on the console in both directions. And, above all, you know exactly what your program lacks: it does not repeat, it does not decide, it does not validate and it remembers nothing.

In module 2, Control Flow, you will start to fix that: with if and switch your program will make real decisions, with while and for it will process as many returns as needed, with break and continue it will fine-tune that control, you will learn to debug step by step to see what happens inside your code, and it will all culminate in the BiblioTech interactive menu, the version that no longer ends after a single loan.

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