JAVA 7

Phase 7 — Production Java Engineering

This phase takes you from "I can build a Spring Boot application" to "I understand how professional Java applications are secured, tested, maintained, debugged, containerized, deployed, and operated."

I’m dividing Phase 7 into 7 Topics and 35 meaningful chapters. Each chapter has exactly one prediction question + one main commented Java snippet, as requested.


Topics in Phase 7

Topic 1 — Clean Code & Professional Design

  1. Clean Code

  2. Naming

  3. Functions and Methods

  4. Comments and Documentation

  5. DRY

  6. KISS

  7. Composition over Inheritance

Topic 2 — SOLID Principles

  1. Single Responsibility Principle

  2. Open/Closed Principle

  3. Liskov Substitution Principle

  4. Interface Segregation Principle

  5. Dependency Inversion Principle

Topic 3 — Design Patterns

  1. Understanding Design Patterns

  2. Factory Pattern

  3. Builder Pattern

  4. Strategy Pattern

  5. Adapter Pattern

  6. Observer Pattern

  7. Template Method Pattern

Topic 4 — Automated Testing

  1. Testing Fundamentals

  2. JUnit Tests

  3. Assertions and Test Lifecycle

  4. Parameterized Tests

  5. Mockito and Mocking

  6. Testing Spring Boot Applications

  7. Integration Testing

  8. Testcontainers

Topic 5 — Spring Security

  1. Authentication vs Authorization

  2. Password Hashing

  3. Spring Security Filter Chain

  4. Roles and Authorities

  5. JWT Authentication

  6. CORS and CSRF

  7. OAuth2 Concepts and Secure API Design

Topic 6 — Professional Development Workflow

  1. Git and Professional Code Workflow

Topic 7 — Production Engineering

  1. Debugging

  2. Logging

  3. Configuration and Secrets

  4. Docker

  5. Docker Compose

  6. CI/CD

  7. Monitoring and Health Checks

  8. Observability

  9. Production Error Handling


Topic 1 — Clean Code & Professional Design

Chapter 1 — Clean Code

Question

Given below is a code snippet that:

  • Separates business logic into meaningful methods.

  • Uses methods with focused responsibilities.

  • Avoids unnecessary complexity.

  • Makes the code easier to understand.

What should be the output of the following code?

public class OrderService {

    public static void main(String[] args) {

        // Create an order containing two products.
        double total = calculateTotal(100, 3);

        // Check whether the customer qualifies for free delivery.
        boolean freeDelivery = qualifiesForFreeDelivery(total);

        // Calculate the final amount.
        double finalAmount = calculateFinalAmount(total, freeDelivery);

        // Display the result.
        System.out.println(finalAmount);
    }

    // Calculate the order total.
    static double calculateTotal(double price, int quantity) {
        return price * quantity;
    }

    // Decide whether delivery should be free.
    static boolean qualifiesForFreeDelivery(double total) {
        return total >= 250;
    }

    // Add delivery charge only when necessary.
    static double calculateFinalAmount(double total, boolean freeDelivery) {
        if (freeDelivery) {
            return total;
        }

        return total + 50;
    }
}

Answer

300.0

Step-by-step explanation

  1. calculateTotal(100, 3) multiplies 100 × 3.

  2. The result is 300.

  3. qualifiesForFreeDelivery(300) checks whether 300 >= 250.

  4. That condition is true.

  5. Therefore calculateFinalAmount() returns the original 300.

  6. System.out.println() prints 300.0.

How to read the important code

"Calculate the order total, check free delivery, then calculate the final amount."

The important idea is that the program reads almost like a sequence of business instructions.

Beginner trap

Clean code does not mean "shortest possible code." It means code that is easy to understand, change, test, and maintain.

Key takeaway

Good code should communicate its intention clearly.


Chapter 2 — Naming

Question

Given below is a code snippet that:

  • Uses descriptive variable names.

  • Uses a descriptive method name.

  • Shows how naming communicates intent.

What should be the output of the following code?

public class Invoice {

    public static void main(String[] args) {

        // Store the product price.
        double productPrice = 500;

        // Store how many products the customer ordered.
        int quantityOrdered = 2;

        // Calculate the total invoice amount.
        double totalInvoiceAmount =
                calculateInvoiceTotal(productPrice, quantityOrdered);

        // Display the result.
        System.out.println(totalInvoiceAmount);
    }

    // Calculate the total price of the invoice.
    static double calculateInvoiceTotal(double productPrice, int quantityOrdered) {
        return productPrice * quantityOrdered;
    }
}

Answer

1000.0

Step-by-step explanation

  1. productPrice contains 500.

  2. quantityOrdered contains 2.

  3. The method receives both values.

  4. It calculates 500 × 2.

  5. The result is 1000.

  6. Java prints 1000.0 because the calculation uses double.

How to read the important code

"Calculate invoice total using product price and quantity ordered."

Compare that with names such as x, n, and calc(). Those names force you to inspect the code to understand it.

Beginner trap

A long name is not automatically a good name. customerHasActiveSubscription is useful; something like customerHasActiveSubscriptionAndIsEligibleFor... may become unnecessarily cumbersome.

Key takeaway

Names should tell another programmer what the data or method means.


Chapter 3 — Functions and Methods

Question

Given below is a code snippet that:

  • Uses small methods.

  • Passes data through parameters.

  • Returns calculated values.

  • Separates different operations.

What should be the output of the following code?

public class ShoppingCart {

    public static void main(String[] args) {

        // Calculate the price of three items.
        double subtotal = calculateSubtotal(200, 100, 50);

        // Calculate the tax.
        double tax = calculateTax(subtotal, 10);

        // Calculate the final amount.
        double total = calculateTotal(subtotal, tax);

        // Display the final amount.
        System.out.println(total);
    }

    // Add all product prices.
    static double calculateSubtotal(double first, double second, double third) {
        return first + second + third;
    }

    // Calculate tax using a percentage.
    static double calculateTax(double amount, double taxRate) {
        return amount * taxRate / 100;
    }

    // Add tax to the subtotal.
    static double calculateTotal(double subtotal, double tax) {
        return subtotal + tax;
    }
}

Answer

385.0

Step-by-step explanation

  1. Subtotal = 200 + 100 + 50 = 350.

  2. Tax = 350 × 10 / 100 = 35.

  3. Total = 350 + 35 = 385.

  4. Java prints 385.0.

How to read the important code

"Calculate the subtotal, calculate the tax, then calculate the total."

Beginner trap

A method should generally perform one understandable job. A giant method that validates a user, calculates payment, sends email, writes to a database, and logs everything becomes difficult to maintain.

Key takeaway

Methods should make a program easier to understand by separating meaningful operations.


Chapter 4 — Comments and Documentation

Question

Given below is a code snippet that:

  • Uses comments to explain intent.

  • Demonstrates the difference between useful and obvious comments.

  • Uses JavaDoc for a public method.

What should be the output of the following code?

public class DiscountService {

    public static void main(String[] args) {

        // Apply a 20% discount to a premium customer's order.
        double finalPrice = calculateDiscountedPrice(1000, 20);

        System.out.println(finalPrice);
    }

    /**
     * Calculates the price after applying a percentage discount.
     *
     * @param originalPrice original price before discount
     * @param discountPercent discount percentage
     * @return price after discount
     */
    static double calculateDiscountedPrice(
            double originalPrice,
            double discountPercent) {

        // Convert the percentage into a decimal.
        double discount = discountPercent / 100;

        // Remove the discount from the original price.
        return originalPrice - (originalPrice * discount);
    }
}

Answer

800.0

Step-by-step explanation

  1. Original price = 1000.

  2. Discount percentage = 20.

  3. 20 / 100 gives 0.2.

  4. Discount amount = 1000 × 0.2 = 200.

  5. Final price = 1000 - 200 = 800.

  6. Java prints 800.0.

How to read the important code

"Calculate the discounted price from the original price and discount percentage."

Beginner trap

Don't write comments such as:

// Add 10 to x.
x = x + 10;

The code already says that.

Useful comments explain why something is being done, especially when the reason isn't obvious from the code.

Key takeaway

Good comments explain intent, not obvious syntax.


Chapter 5 — DRY

Question

Given below is a code snippet that:

  • Avoids repeating the same calculation.

  • Centralizes discount logic in one method.

  • Reuses that method for multiple products.

What should be the output of the following code?

public class PricingService {

    public static void main(String[] args) {

        // Calculate discounted prices using the same rule.
        double laptopPrice = applyDiscount(1000, 10);
        double phonePrice = applyDiscount(500, 10);

        // Add the two prices.
        double total = laptopPrice + phonePrice;

        System.out.println(total);
    }

    // Keep discount logic in one place.
    static double applyDiscount(double price, double percentage) {
        return price - (price * percentage / 100);
    }
}

Answer

1350.0

Step-by-step explanation

  1. Laptop price after 10% discount = 900.

  2. Phone price after 10% discount = 450.

  3. Total = 900 + 450.

  4. Result = 1350.

How to read the important code

"Apply the same discount function to each product."

Beginner trap

DRY means Don't Repeat Yourself, but it doesn't mean every two similar lines must be merged into an abstraction. Excessive abstraction can make code harder to understand.

Key takeaway

Put genuinely shared logic in one place so changes don't have to be made repeatedly.


Chapter 6 — KISS

Question

Given below is a code snippet that:

  • Uses simple conditional logic.

  • Avoids unnecessary abstractions.

  • Solves the business rule directly.

What should be the output of the following code?

public class ShippingService {

    public static void main(String[] args) {

        // Store the order amount.
        double orderAmount = 1200;

        // Decide the shipping cost using a simple rule.
        double shippingCost;

        if (orderAmount >= 1000) {
            shippingCost = 0;
        } else {
            shippingCost = 100;
        }

        // Display the shipping cost.
        System.out.println(shippingCost);
    }
}

Answer

0.0

Step-by-step explanation

  1. orderAmount is 1200.

  2. Java checks orderAmount >= 1000.

  3. The condition is true.

  4. Shipping cost becomes 0.

  5. Java prints 0.0.

How to read the important code

"If the order is at least 1000, shipping is free; otherwise it costs 100."

Beginner trap

KISS doesn't mean writing primitive code forever. As requirements become more complex, proper abstractions become useful.

Key takeaway

Prefer the simplest design that correctly solves the problem.


Chapter 7 — Composition over Inheritance

Question

Given below is a code snippet that:

  • Uses composition.

  • Gives one object another object as a dependency.

  • Avoids creating an unnecessary inheritance relationship.

What should be the output of the following code?

public class CompositionExample {

    public static void main(String[] args) {

        // Create an email sender.
        EmailSender emailSender = new EmailSender();

        // Give the notification service an EmailSender.
        NotificationService notificationService =
                new NotificationService(emailSender);

        // Ask the notification service to send a notification.
        notificationService.notifyUser("Order shipped");

    }
}

class EmailSender {

    // Send an email.
    void send(String message) {
        System.out.println("Email: " + message);
    }
}

class NotificationService {

    // Store the object this service uses.
    private final EmailSender emailSender;

    // Receive the dependency through the constructor.
    NotificationService(EmailSender emailSender) {
        this.emailSender = emailSender;
    }

    // Use the composed EmailSender object.
    void notifyUser(String message) {
        emailSender.send(message);
    }
}

Answer

Email: Order shipped

Step-by-step explanation

  1. new EmailSender() creates an EmailSender object.

  2. That object is passed into NotificationService.

  3. The service stores the object in emailSender.

  4. notifyUser() calls emailSender.send().

  5. The message is printed.

How to read the important code

"Create a notification service using an email sender."

NotificationService has an EmailSender. It does not is an EmailSender.

Beginner trap

Inheritance represents an is-a relationship. Composition usually represents a has-a/uses-a relationship.

Key takeaway

Prefer putting objects together when you need collaboration instead of using inheritance just for code reuse.


Topic 2 — SOLID Principles

Chapter 8 — Single Responsibility Principle

Question

Given below is a code snippet that:

  • Separates invoice calculation from email sending.

  • Gives each class a focused responsibility.

  • Demonstrates SRP.

What should be the output of the following code?

public class SRPExample {

    public static void main(String[] args) {

        // Create separate objects for separate responsibilities.
        InvoiceCalculator calculator = new InvoiceCalculator();
        EmailService emailService = new EmailService();

        // Calculate the invoice.
        double total = calculator.calculate(500, 2);

        // Send the invoice notification.
        emailService.send("Invoice total: " + total);
    }
}

class InvoiceCalculator {

    // Responsible only for invoice calculation.
    double calculate(double price, int quantity) {
        return price * quantity;
    }
}

class EmailService {

    // Responsible only for sending email.
    void send(String message) {
        System.out.println(message);
    }
}

Answer

Invoice total: 1000.0

Step-by-step explanation

  1. InvoiceCalculator calculates 500 × 2.

  2. The result is 1000.

  3. EmailService receives the message.

  4. It prints the message.

How to read the important code

"The calculator calculates; the email service sends."

Beginner trap

"One responsibility" does not necessarily mean "one method." It means the class should have one coherent reason to change.

Key takeaway

A class should have one clear responsibility.


Chapter 9 — Open/Closed Principle

Question

Given below is a code snippet that:

  • Uses an interface for discount rules.

  • Adds a new discount without modifying the existing calculator.

  • Demonstrates extension without changing core logic.

What should be the output of the following code?

public class OCPExample {

    public static void main(String[] args) {

        // Create a discount strategy.
        Discount discount = new TenPercentDiscount();

        // Give the calculator the discount implementation.
        PriceCalculator calculator = new PriceCalculator(discount);

        // Calculate the final price.
        System.out.println(calculator.calculate(1000));
    }
}

interface Discount {

    // Every discount must provide a discount calculation.
    double apply(double price);
}

class TenPercentDiscount implements Discount {

    // Apply a 10% discount.
    public double apply(double price) {
        return price * 0.90;
    }
}

class PriceCalculator {

    // Store the discount rule.
    private final Discount discount;

    PriceCalculator(Discount discount) {
        this.discount = discount;
    }

    // Use whichever discount implementation was supplied.
    double calculate(double price) {
        return discount.apply(price);
    }
}

Answer

900.0

Step-by-step explanation

  1. A TenPercentDiscount object is created.

  2. It is supplied to PriceCalculator.

  3. The calculator calls discount.apply(1000).

  4. The discount returns 900.

  5. The result is printed.

How to read the important code

"The calculator works with the Discount interface rather than knowing the exact discount type."

Beginner trap

The Open/Closed Principle does not mean "never modify code." It means important parts should be designed so new behavior can often be added without modifying stable existing logic.

Key takeaway

Design stable code so new behavior can be added through extension.


Chapter 10 — Liskov Substitution Principle

Question

Given below is a code snippet that:

  • Defines a common contract.

  • Allows different implementations to be used interchangeably.

  • Demonstrates substitutability.

What should be the output of the following code?

public class LSPExample {

    public static void main(String[] args) {

        // Use the common PaymentProcessor type.
        PaymentProcessor processor = new CardPaymentProcessor();

        // The caller does not need to know the exact implementation.
        processor.pay(500);
    }
}

interface PaymentProcessor {

    // Every implementation promises to process a payment.
    void pay(double amount);
}

class CardPaymentProcessor implements PaymentProcessor {

    // Fulfill the PaymentProcessor contract.
    public void pay(double amount) {
        System.out.println("Card payment: " + amount);
    }
}

Answer

Card payment: 500.0

Step-by-step explanation

  1. CardPaymentProcessor implements PaymentProcessor.

  2. Therefore it can be stored in a PaymentProcessor variable.

  3. The program calls pay(500).

  4. The card implementation performs the operation.

  5. The result is printed.

How to read the important code

"Use a payment processor, which happens to be a card payment processor."

Beginner trap

LSP is not simply "every child class can technically extend the parent." The child must preserve the expectations of the parent's contract.

Key takeaway

A subtype should be usable wherever its parent type is expected without breaking the program's assumptions.


Chapter 11 — Interface Segregation Principle

Question

Given below is a code snippet that:

  • Uses small focused interfaces.

  • Prevents a class from implementing methods it doesn't need.

  • Demonstrates interface segregation.

What should be the output of the following code?

public class ISPExample {

    public static void main(String[] args) {

        // Create a printer that only needs printing capability.
        Printer printer = new SimplePrinter();

        // Use the small interface.
        printer.print("Invoice");
    }
}

interface Printer {

    // Printing capability.
    void print(String document);
}

class SimplePrinter implements Printer {

    // This class only needs to support printing.
    public void print(String document) {
        System.out.println("Printing: " + document);
    }
}

Answer

Printing: Invoice

Step-by-step explanation

  1. Printer contains only the print() operation.

  2. SimplePrinter implements that interface.

  3. It doesn't need to implement unrelated operations such as scanning or faxing.

  4. print() displays the document.

Key takeaway

Prefer small interfaces focused on what clients actually need.


Chapter 12 — Dependency Inversion Principle

Question

Given below is a code snippet that:

  • Depends on an abstraction.

  • Injects a concrete implementation.

  • Demonstrates dependency inversion.

What should be the output of the following code?

public class DIPExample {

    public static void main(String[] args) {

        // Create a concrete message sender.
        MessageSender sender = new EmailMessageSender();

        // Give the high-level service the abstraction.
        NotificationService service = new NotificationService(sender);

        // Send the notification.
        service.notifyUser("Welcome");
    }
}

interface MessageSender {

    // Define what a sender must do.
    void send(String message);
}

class EmailMessageSender implements MessageSender {

    // Provide email-specific behavior.
    public void send(String message) {
        System.out.println("Email: " + message);
    }
}

class NotificationService {

    // Depend on the abstraction, not the concrete email class.
    private final MessageSender sender;

    // Receive the dependency from outside.
    NotificationService(MessageSender sender) {
        this.sender = sender;
    }

    void notifyUser(String message) {
        sender.send(message);
    }
}

Answer

Email: Welcome

Step-by-step explanation

  1. MessageSender is an abstraction.

  2. EmailMessageSender implements it.

  3. NotificationService depends on MessageSender, not specifically on EmailMessageSender.

  4. The email implementation is supplied from outside.

  5. The notification service uses the interface.

Key takeaway

High-level business code should depend on abstractions rather than tightly coupling itself to concrete implementations.


Topic 3 — Design Patterns

Chapter 13 — Understanding Design Patterns

Question

Given below is a code snippet that:

  • Uses an interface to define interchangeable behavior.

  • Separates the caller from implementation details.

  • Demonstrates the basic idea behind a design pattern.

What should be the output of the following code?

public class PatternExample {

    public static void main(String[] args) {

        // Choose an implementation.
        Formatter formatter = new JsonFormatter();

        // The caller works with the abstraction.
        String result = formatter.format("Alice");

        System.out.println(result);
    }
}

interface Formatter {

    // Define the common operation.
    String format(String name);
}

class JsonFormatter implements Formatter {

    // Provide one concrete implementation.
    public String format(String name) {
        return "{\"name\":\"" + name + "\"}";
    }
}

Answer

{"name":"Alice"}

Step-by-step explanation

  1. Formatter defines a common contract.

  2. JsonFormatter implements it.

  3. The variable uses the interface type.

  4. The implementation formats "Alice" as JSON-like text.

  5. The result is printed.

Key takeaway

Design patterns are reusable design solutions, not magic Java syntax.


Chapter 14 — Factory Pattern

Question

Given below is a code snippet that:

  • Uses a factory to create objects.

  • Hides object-creation details.

  • Returns an interface type.

What should be the output of the following code?

public class FactoryExample {

    public static void main(String[] args) {

        // Ask the factory for a notification service.
        Notification notification =
                NotificationFactory.create("email");

        // Use the returned object.
        notification.send("Order shipped");
    }
}

interface Notification {

    // Common notification operation.
    void send(String message);
}

class EmailNotification implements Notification {

    // Send an email notification.
    public void send(String message) {
        System.out.println("Email: " + message);
    }
}

class NotificationFactory {

    // Create the appropriate implementation.
    static Notification create(String type) {

        if (type.equals("email")) {
            return new EmailNotification();
        }

        throw new IllegalArgumentException("Unknown notification type");
    }
}

Answer

Email: Order shipped

Step-by-step explanation

  1. The factory receives "email".

  2. It creates an EmailNotification.

  3. The returned object is stored in a Notification variable.

  4. send() is called.

  5. The message is printed.

Key takeaway

A Factory centralizes and hides object-creation decisions.


Chapter 15 — Builder Pattern

Question

Given below is a code snippet that:

  • Builds an object step by step.

  • Uses chained method calls.

  • Handles optional values more clearly.

What should be the output of the following code?

public class BuilderExample {

    public static void main(String[] args) {

        // Build a User object step by step.
        User user = new User.Builder()
                .name("Alice")
                .email("alice@example.com")
                .age(30)
                .build();

        // Display selected values.
        System.out.println(user.name + " " + user.age);
    }
}

class User {

    final String name;
    final String email;
    final int age;

    private User(Builder builder) {

        // Copy builder values into the final object.
        this.name = builder.name;
        this.email = builder.email;
        this.age = builder.age;
    }

    static class Builder {

        String name;
        String email;
        int age;

        // Set the user's name.
        Builder name(String name) {
            this.name = name;
            return this;
        }

        // Set the user's email.
        Builder email(String email) {
            this.email = email;
            return this;
        }

        // Set the user's age.
        Builder age(int age) {
            this.age = age;
            return this;
        }

        // Create the final User object.
        User build() {
            return new User(this);
        }
    }
}

Answer

Alice 30

Step-by-step explanation

  1. new User.Builder() creates a builder.

  2. .name("Alice") stores the name.

  3. .email(...) stores the email.

  4. .age(30) stores the age.

  5. .build() creates the actual User.

  6. The program prints the name and age.

Key takeaway

Builder is useful when an object has many configuration options and construction would otherwise become difficult to read.


Chapter 16 — Strategy Pattern

Question

Given below is a code snippet that:

  • Defines interchangeable algorithms.

  • Selects one strategy at runtime.

  • Keeps the main service independent of the algorithm.

What should be the output of the following code?

public class StrategyExample {

    public static void main(String[] args) {

        // Choose the discount algorithm.
        DiscountStrategy strategy = new PremiumDiscount();

        // Give the strategy to the calculator.
        DiscountCalculator calculator =
                new DiscountCalculator(strategy);

        // Calculate the discounted price.
        System.out.println(calculator.calculate(1000));
    }
}

interface DiscountStrategy {

    // Every strategy calculates a discounted price.
    double apply(double price);
}

class PremiumDiscount implements DiscountStrategy {

    // Premium customers receive 20% off.
    public double apply(double price) {
        return price * 0.80;
    }
}

class DiscountCalculator {

    private final DiscountStrategy strategy;

    DiscountCalculator(DiscountStrategy strategy) {
        this.strategy = strategy;
    }

    // Delegate discount calculation to the chosen strategy.
    double calculate(double price) {
        return strategy.apply(price);
    }
}

Answer

800.0

Key takeaway

Strategy lets you swap an algorithm without rewriting the code that uses it.


Chapter 17 — Adapter Pattern

Question

Given below is a code snippet that:

  • Uses an adapter between incompatible interfaces.

  • Allows existing code to work with a new API.

  • Demonstrates interface translation.

What should be the output of the following code?

public class AdapterExample {

    public static void main(String[] args) {

        // Create the existing third-party service.
        LegacyPaymentService legacyService =
                new LegacyPaymentService();

        // Adapt the old API to our application's interface.
        PaymentGateway gateway =
                new PaymentAdapter(legacyService);

        // Use our application's standard interface.
        gateway.pay(500);
    }
}

interface PaymentGateway {

    // Our application's payment contract.
    void pay(double amount);
}

class LegacyPaymentService {

    // Old API uses a different method name.
    void makePayment(double amount) {
        System.out.println("Paid: " + amount);
    }
}

class PaymentAdapter implements PaymentGateway {

    private final LegacyPaymentService legacyService;

    PaymentAdapter(LegacyPaymentService legacyService) {
        this.legacyService = legacyService;
    }

    // Translate our API call into the legacy API call.
    public void pay(double amount) {
        legacyService.makePayment(amount);
    }
}

Answer

Paid: 500.0

Key takeaway

Adapter allows incompatible interfaces to work together without changing the existing code.


Chapter 18 — Observer Pattern

Question

Given below is a code snippet that:

  • Maintains a list of listeners.

  • Notifies them when an event occurs.

  • Demonstrates event-based communication.

What should be the output of the following code?

import java.util.ArrayList;
import java.util.List;

public class ObserverExample {

    public static void main(String[] args) {

        // Create the event source.
        OrderService orderService = new OrderService();

        // Register two listeners.
        orderService.addListener(
                message -> System.out.println("Email: " + message));

        orderService.addListener(
                message -> System.out.println("Log: " + message));

        // Trigger an event.
        orderService.placeOrder();
    }
}

class OrderService {

    // Store all interested listeners.
    private final List<OrderListener> listeners = new ArrayList<>();

    // Register a listener.
    void addListener(OrderListener listener) {
        listeners.add(listener);
    }

    // Notify every listener when the order is placed.
    void placeOrder() {

        System.out.println("Order placed");

        for (OrderListener listener : listeners) {
            listener.onOrderPlaced("Order #101");
        }
    }
}

interface OrderListener {

    // Called when an order is placed.
    void onOrderPlaced(String orderId);
}

Answer

Order placed
Email: Order #101
Log: Order #101

Step-by-step explanation

  1. Two listeners are registered.

  2. placeOrder() prints Order placed.

  3. The first listener is notified.

  4. It prints the email message.

  5. The second listener is notified.

  6. It prints the log message.

Key takeaway

Observer allows one object to notify multiple interested objects when something happens.


Chapter 19 — Template Method Pattern

Question

Given below is a code snippet that:

  • Defines a fixed processing sequence.

  • Allows subclasses to customize one step.

  • Demonstrates the Template Method pattern.

What should be the output of the following code?

public class TemplateMethodExample {

    public static void main(String[] args) {

        // Choose a concrete report generator.
        ReportGenerator generator =
                new SalesReportGenerator();

        // Run the fixed report process.
        generator.generate();
    }
}

abstract class ReportGenerator {

    // Template method defines the overall sequence.
    final void generate() {

        // Step 1 is fixed.
        loadData();

        // Step 2 can vary.
        formatData();

        // Step 3 is fixed.
        saveReport();
    }

    void loadData() {
        System.out.println("Loading data");
    }

    // Subclasses customize this step.
    abstract void formatData();

    void saveReport() {
        System.out.println("Saving report");
    }
}

class SalesReportGenerator extends ReportGenerator {

    // Customize only the formatting step.
    void formatData() {
        System.out.println("Formatting sales data");
    }
}

Answer

Loading data
Formatting sales data
Saving report

Key takeaway

Template Method fixes the overall algorithm while allowing selected steps to vary.


Topic 4 — Automated Testing

Chapter 20 — Testing Fundamentals

Question

Given below is a code snippet that:

  • Separates application code from test logic.

  • Tests a deterministic method.

  • Demonstrates the basic idea of an automated test.

What should be the output of the following code?

public class TestingFundamentals {

    public static void main(String[] args) {

        // Run the method that we want to test.
        int result = Calculator.add(10, 20);

        // Check the expected result.
        if (result == 30) {
            System.out.println("TEST PASSED");
        } else {
            System.out.println("TEST FAILED");
        }
    }
}

class Calculator {

    // Method containing the business logic.
    static int add(int first, int second) {
        return first + second;
    }
}

Answer

TEST PASSED

Step-by-step explanation

  1. Calculator.add(10, 20) returns 30.

  2. The program compares 30 with the expected value 30.

  3. They are equal.

  4. Therefore the test passes.

Key takeaway

A test automatically checks whether actual behavior matches expected behavior.


Chapter 21 — JUnit Tests

Question

Given below is a code snippet that:

  • Uses JUnit.

  • Tests a Java method.

  • Uses an assertion.

What should be the result when this test runs?

import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;

class CalculatorTest {

    @Test
    void shouldAddTwoNumbers() {

        // Call the real application method.
        int result = Calculator.add(10, 20);

        // Verify the expected result.
        assertEquals(30, result);
    }
}

class Calculator {

    // Application logic being tested.
    static int add(int first, int second) {
        return first + second;
    }
}

Answer

Test passes

Step-by-step explanation

  1. @Test tells JUnit that the method is a test.

  2. Calculator.add() returns 30.

  3. assertEquals(30, result) compares expected and actual values.

  4. Both are 30.

  5. Therefore the test passes.

Key takeaway

JUnit provides the framework for automatically running and verifying Java tests.


Chapter 22 — Assertions and Test Lifecycle

Question

Given below is a code snippet that:

  • Uses a setup method.

  • Runs setup before the test.

  • Checks a result with an assertion.

What should be the result when this test runs?

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class UserServiceTest {

    private UserService service;

    @BeforeEach
    void setUp() {

        // Create a fresh service before each test.
        service = new UserService();
    }

    @Test
    void shouldReturnWelcomeMessage() {

        // Call the service.
        String result = service.welcome("Alice");

        // Verify the expected value.
        assertEquals("Welcome Alice", result);
    }
}

class UserService {

    // Create the expected welcome message.
    String welcome(String name) {
        return "Welcome " + name;
    }
}

Answer

Test passes

Step-by-step explanation

  1. JUnit executes setUp() before the test.

  2. A new UserService is created.

  3. The test calls welcome("Alice").

  4. It returns "Welcome Alice".

  5. The assertion succeeds.

Key takeaway

Test lifecycle methods prepare and clean up the environment around tests.


Chapter 23 — Parameterized Tests

Question

Given below is a code snippet that:

  • Runs the same test with different input values.

  • Uses @ParameterizedTest.

  • Uses multiple test cases without duplicating the test method.

What should be the result when this test runs?

import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class PasswordValidatorTest {

    @ParameterizedTest

    // Run the same test using each supplied password.
    @ValueSource(strings = {"Strong123", "Java2026", "Backend99"})
    void shouldAcceptValidPasswords(String password) {

        // Validate the password.
        boolean valid = PasswordValidator.isValid(password);

        // Every supplied password should be valid.
        assertTrue(valid);
    }
}

class PasswordValidator {

    static boolean isValid(String password) {

        // A simple demonstration rule.
        return password.length() >= 8;
    }
}

Answer

All parameterized test cases pass

Step-by-step explanation

  1. JUnit runs the test once for each supplied string.

  2. "Strong123" has at least 8 characters.

  3. "Java2026" has at least 8 characters.

  4. "Backend99" has at least 8 characters.

  5. Every assertion succeeds.

Key takeaway

Parameterized tests let one test verify many input combinations.


Chapter 24 — Mockito and Mocking

Question

Given below is a code snippet that:

  • Creates a mock dependency.

  • Defines what the mock should return.

  • Tests a service without using the real repository.

What should be the result when this test runs?

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.junit.jupiter.api.Test;

class UserServiceTest {

    @Test
    void shouldReturnUserName() {

        // Create a fake repository controlled by the test.
        UserRepository repository = mock(UserRepository.class);

        // Tell the mock what to return for a specific call.
        when(repository.findNameById(10))
                .thenReturn("Alice");

        // Inject the fake repository into the service.
        UserService service = new UserService(repository);

        // Call the service.
        String result = service.getUserName(10);

        // Verify the expected behavior.
        assertEquals("Alice", result);
    }
}

interface UserRepository {

    // Repository operation.
    String findNameById(int id);
}

class UserService {

    private final UserRepository repository;

    UserService(UserRepository repository) {
        this.repository = repository;
    }

    // Ask the repository for the user's name.
    String getUserName(int id) {
        return repository.findNameById(id);
    }
}

Answer

Test passes

Step-by-step explanation

  1. Mockito creates a mock UserRepository.

  2. The test says: when ID 10 is requested, return "Alice".

  3. UserService receives that mock.

  4. The service asks the repository for ID 10.

  5. The mock returns "Alice".

  6. The assertion succeeds.

Key takeaway

Mocking lets you test one component while controlling its dependencies.


Chapter 25 — Testing Spring Boot Applications

Question

Given below is a code snippet that:

  • Uses Spring Boot's test support.

  • Loads application components.

  • Tests a Spring-managed service.

What should be the result when this test runs?

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class GreetingServiceTest {

    // Ask Spring to inject the service bean.
    @Autowired
    private GreetingService greetingService;

    @Test
    void shouldCreateGreeting() {

        // Call the Spring-managed service.
        String result = greetingService.greet("Alice");

        // Verify the result.
        assertEquals("Hello Alice", result);
    }
}

class GreetingService {

    // Create a greeting.
    String greet(String name) {
        return "Hello " + name;
    }
}

Answer

Test passes

Step-by-step explanation

  1. @SpringBootTest asks Spring Boot to load the application test context.

  2. @Autowired asks Spring to provide a dependency.

  3. The service method is called.

  4. It returns "Hello Alice".

  5. The assertion succeeds.

Key takeaway

Spring tests can verify code together with Spring's dependency-injection environment.


Chapter 26 — Integration Testing

Question

Given below is a code snippet that:

  • Tests multiple components together.

  • Uses a repository and service together.

  • Demonstrates the difference between isolated unit testing and integration testing.

What should be the result when this test runs?

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

class UserIntegrationTest {

    @Test
    void shouldCreateAndFindUser() {

        // Create the real repository implementation.
        UserRepository repository = new UserRepository();

        // Give the repository to the service.
        UserService service = new UserService(repository);

        // Create a user through the service.
        service.createUser("Alice");

        // Read the user back.
        String result = service.findUser();

        // Verify that the components work together.
        assertEquals("Alice", result);
    }
}

class UserRepository {

    private String name;

    void save(String name) {
        this.name = name;
    }

    String find() {
        return name;
    }
}

class UserService {

    private final UserRepository repository;

    UserService(UserRepository repository) {
        this.repository = repository;
    }

    void createUser(String name) {
        repository.save(name);
    }

    String findUser() {
        return repository.find();
    }
}

Answer

Test passes

Step-by-step explanation

  1. The real repository is created.

  2. The real service uses that repository.

  3. The service saves "Alice".

  4. The service retrieves the value.

  5. "Alice" is returned.

  6. The assertion succeeds.

Key takeaway

Integration tests verify that multiple real components work correctly together.


Chapter 27 — Testcontainers

Question

Given below is a code snippet that:

  • Represents the concept of starting a real database container for tests.

  • Uses Testcontainers.

  • Demonstrates why integration tests can use real infrastructure.

What should be the result when this test runs?

import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;

class DatabaseIntegrationTest {

    @Test
    void shouldStartDatabase() {

        // Create a temporary PostgreSQL container for the test.
        try (PostgreSQLContainer<?> database =
                     new PostgreSQLContainer<>("postgres:16")) {

            // Start the temporary database.
            database.start();

            // Confirm that the database is running.
            System.out.println(database.isRunning());
        }

        // The try-with-resources block stops the container.
    }
}

Answer

true

Step-by-step explanation

  1. Testcontainers prepares a PostgreSQL container.

  2. database.start() starts it.

  3. isRunning() returns true.

  4. The result is printed.

  5. When the try block ends, the resource is closed.

Key takeaway

Testcontainers allows integration tests to use realistic disposable infrastructure such as databases.


Topic 5 — Spring Security

Chapter 28 — Authentication vs Authorization

Question

Given below is a code snippet that:

  • Checks whether a user is authenticated.

  • Checks whether the authenticated user has a required role.

  • Demonstrates authentication vs authorization.

What should be the output of the following code?

public class SecurityConcepts {

    public static void main(String[] args) {

        // Create an authenticated user with the USER role.
        User user = new User(true, "USER");

        // Check identity first.
        if (!user.authenticated) {
            System.out.println("Not authenticated");
            return;
        }

        // Then check permission.
        if (user.role.equals("ADMIN")) {
            System.out.println("Access granted");
        } else {
            System.out.println("Access denied");
        }
    }
}

class User {

    boolean authenticated;
    String role;

    User(boolean authenticated, String role) {
        this.authenticated = authenticated;
        this.role = role;
    }
}

Answer

Access denied

Step-by-step explanation

  1. The user is authenticated.

  2. Therefore identity verification succeeds.

  3. The user has the USER role.

  4. The code requires ADMIN.

  5. Therefore authorization fails.

Key takeaway

Authentication asks "Who are you?" Authorization asks "Are you allowed to do this?"


Chapter 29 — Password Hashing

Question

Given below is a code snippet that:

  • Demonstrates one-way password hashing conceptually.

  • Does not store the original password.

  • Compares a supplied password with a stored hash.

What should be the output of the following code?

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

public class PasswordHashing {

    public static void main(String[] args) throws Exception {

        // The password entered by the user.
        String password = "secret123";

        // Store only the hash, not the original password.
        String storedHash = hash(password);

        // Hash the password entered during login.
        String loginHash = hash("secret123");

        // Compare hashes.
        System.out.println(storedHash.equals(loginHash));
    }

    static String hash(String password) throws Exception {

        // Create a SHA-256 hashing algorithm.
        MessageDigest digest =
                MessageDigest.getInstance("SHA-256");

        // Convert the password into bytes and hash it.
        byte[] bytes =
                digest.digest(password.getBytes(StandardCharsets.UTF_8));

        // Convert the hash bytes to hexadecimal text.
        StringBuilder result = new StringBuilder();

        for (byte value : bytes) {
            result.append(String.format("%02x", value));
        }

        return result.toString();
    }
}

Answer

true

Step-by-step explanation

  1. "secret123" is hashed.

  2. The hash is stored.

  3. The same password is hashed again during login.

  4. The same input produces the same hash here.

  5. The hashes match.

Beginner trap

For real password storage, don't implement password security yourself with plain SHA-256. Frameworks such as Spring Security use password encoders designed for password hashing, such as BCrypt, Argon2, or PBKDF2.

Key takeaway

Production applications should store secure password hashes, never plaintext passwords.


Chapter 30 — Spring Security Filter Chain

Question

Given below is a code snippet that:

  • Represents the idea of request filtering.

  • Checks authentication before allowing access.

  • Demonstrates the role of security filters.

What should be the output of the following code?

public class SecurityFilterExample {

    public static void main(String[] args) {

        // Create a request with an authentication token.
        Request request = new Request("VALID_TOKEN");

        // Pass the request through the security filter.
        SecurityFilter filter = new SecurityFilter();

        filter.process(request);
    }
}

class Request {

    String token;

    Request(String token) {
        this.token = token;
    }
}

class SecurityFilter {

    void process(Request request) {

        // Check authentication before application logic.
        if ("VALID_TOKEN".equals(request.token)) {
            System.out.println("Authenticated");
            System.out.println("Request allowed");
        } else {
            System.out.println("Unauthorized");
        }
    }
}

Answer

Authenticated
Request allowed

Step-by-step explanation

  1. The request contains "VALID_TOKEN".

  2. The filter checks the token.

  3. The token matches.

  4. Authentication succeeds.

  5. The request is allowed to continue.

Key takeaway

Spring Security processes requests through security infrastructure before protected application code is allowed to execute.


Chapter 31 — Roles and Authorities

Question

Given below is a code snippet that:

  • Represents a user with authorities.

  • Checks whether a required authority exists.

  • Demonstrates permission-based access.

What should be the output of the following code?

import java.util.Set;

public class AuthorityExample {

    public static void main(String[] args) {

        // Give the user two authorities.
        Set<String> authorities =
                Set.of("READ_USERS", "CREATE_USERS");

        // Check whether the user can delete users.
        if (authorities.contains("DELETE_USERS")) {
            System.out.println("Delete allowed");
        } else {
            System.out.println("Delete denied");
        }
    }
}

Answer

Delete denied

Step-by-step explanation

  1. The user has READ_USERS.

  2. The user has CREATE_USERS.

  3. The user does not have DELETE_USERS.

  4. Therefore deletion is denied.

Key takeaway

Authorities represent specific permissions that can be checked when protecting operations.


Chapter 32 — JWT Authentication

Question

Given below is a code snippet that:

  • Demonstrates the conceptual structure of a JWT.

  • Separates header, payload, and signature.

  • Shows that the token carries claims.

What should be the output of the following code?

public class JwtConcept {

    public static void main(String[] args) {

        // A simplified JWT-like representation.
        String header = "HEADER";
        String payload = "user=alice;role=ADMIN";
        String signature = "SIGNED_VALUE";

        // Join the three conceptual JWT parts.
        String token = header + "." + payload + "." + signature;

        // Display the token.
        System.out.println(token);
    }
}

Answer

HEADER.user=alice;role=ADMIN.SIGNED_VALUE

Step-by-step explanation

  1. A JWT conceptually contains three parts.

  2. The header describes the token type/algorithm information.

  3. The payload contains claims.

  4. The signature protects the token against unauthorized modification.

  5. The parts are represented using dots.

Beginner trap

A JWT is not encryption by default. Its payload can generally be decoded. Its signature is what allows the server to verify that the token hasn't been altered.

Key takeaway

JWTs commonly carry signed claims that a server can validate when processing authenticated requests.


Chapter 33 — CORS and CSRF

Question

Given below is a code snippet that:

  • Represents a simplified cross-origin request check.

  • Demonstrates allowed and disallowed origins.

  • Shows why browser security policies matter.

What should be the output of the following code?

import java.util.Set;

public class CorsExample {

    public static void main(String[] args) {

        // Origins allowed to call the application.
        Set<String> allowedOrigins =
                Set.of("https://myapp.com");

        // Origin of the incoming browser request.
        String requestOrigin = "https://evil.example";

        // Check whether the origin is allowed.
        if (allowedOrigins.contains(requestOrigin)) {
            System.out.println("CORS allowed");
        } else {
            System.out.println("CORS blocked");
        }
    }
}

Answer

CORS blocked

Step-by-step explanation

  1. The application allows https://myapp.com.

  2. The request comes from https://evil.example.

  3. That origin is not in the allowed set.

  4. Therefore the request is blocked by the simplified CORS rule.

Key takeaway

CORS controls which browser origins are permitted to interact with a web application. CSRF addresses a different problem involving unwanted authenticated actions.


Chapter 34 — OAuth2 Concepts and Secure API Design

Question

Given below is a code snippet that:

  • Represents an access token.

  • Checks whether the token exists.

  • Checks whether the required scope is available.

  • Demonstrates the basic idea behind scoped API access.

What should be the output of the following code?

import java.util.Set;

public class OAuthConcept {

    public static void main(String[] args) {

        // Represent information contained in a validated access token.
        String accessToken = "VALID_TOKEN";

        // Permissions granted to this token.
        Set<String> scopes =
                Set.of("read:users", "read:orders");

        // Check whether authentication information exists.
        if (!"VALID_TOKEN".equals(accessToken)) {
            System.out.println("401 Unauthorized");
            return;
        }

        // Check whether this API operation is permitted.
        if (scopes.contains("delete:users")) {
            System.out.println("200 OK");
        } else {
            System.out.println("403 Forbidden");
        }
    }
}

Answer

403 Forbidden

Step-by-step explanation

  1. The token is considered valid.

  2. Therefore authentication succeeds.

  3. The requested operation requires delete:users.

  4. The token only has read:users and read:orders.

  5. Therefore authorization fails.

  6. 403 Forbidden is printed.

Key takeaway

Secure APIs should authenticate the caller and then verify that the caller has permission for the requested operation.


Topic 6 — Professional Development Workflow

Chapter 35 — Git and Professional Code Workflow

Question

Given below is a code snippet that:

  • Represents a simplified Git workflow.

  • Shows the conceptual sequence of modifying, staging, and committing code.

  • Demonstrates why commits represent logical units of work.

What should be the output of the following code?

public class GitWorkflow {

    public static void main(String[] args) {

        // Imagine that a developer changed the login feature.
        String workingTree = "Login feature changed";

        // The developer stages the change.
        String stagingArea = workingTree;

        // The developer creates a commit.
        String commit = stagingArea;

        // Display the committed change.
        System.out.println(commit);
    }
}

Answer

Login feature changed

Step-by-step explanation

Think of Git as maintaining stages of your work:

  1. You modify files → working tree.

  2. You select changes for the next commit → staging area.

  3. You create a commit → saved snapshot in repository history.

  4. Here, the same logical change moves through those stages.

How to read the important code

"Change the code, stage the change, then commit the change."

In professional work, a commit should ideally represent one logical change.

Key takeaway

Git records the history of your code changes and lets developers collaborate safely.


Topic 7 — Production Engineering

Chapter 36 — Debugging

Question

Given below is a code snippet that:

  • Demonstrates a logical bug.

  • Uses a debugger-style inspection point.

  • Shows how inspecting intermediate values can reveal the problem.

What should be the output of the following code?

public class DebuggingExample {

    public static void main(String[] args) {

        // Store the original price.
        double price = 1000;

        // Store the discount percentage.
        double discountPercent = 20;

        // Calculate the discount amount.
        double discount = price * discountPercent / 100;

        // This is the value a developer would inspect in a debugger.
        System.out.println("Discount: " + discount);

        // Calculate the final price.
        double finalPrice = price - discount;

        System.out.println("Final price: " + finalPrice);
    }
}

Answer

Discount: 200.0
Final price: 800.0

Step-by-step explanation

  1. price is 1000.

  2. discountPercent is 20.

  3. Discount = 1000 × 20 / 100 = 200.

  4. Final price = 1000 - 200 = 800.

  5. Printing intermediate values helps verify the calculation.

Key takeaway

Debugging means systematically finding where actual program behavior diverges from expected behavior.


Chapter 37 — Logging

Question

Given below is a code snippet that:

  • Uses Java logging.

  • Records an informational message.

  • Records a warning.

  • Demonstrates why logging is preferable to random console printing in production applications.

What should be the output conceptually?

import java.util.logging.Logger;

public class LoggingExample {

    // Create a logger associated with this class.
    private static final Logger logger =
            Logger.getLogger(LoggingExample.class.getName());

    public static void main(String[] args) {

        // Record a normal application event.
        logger.info("User login successful");

        // Record an unusual situation.
        logger.warning("Login attempt failed");
    }
}

Answer

Conceptually:

INFO: User login successful
WARNING: Login attempt failed

The exact formatting can vary by Java logging configuration.

Step-by-step explanation

  1. A Logger is created.

  2. logger.info() records an informational event.

  3. logger.warning() records a warning.

  4. A logging framework can later route these messages to files, consoles, centralized systems, or monitoring platforms.

Beginner trap

Don't log passwords, access tokens, credit-card information, or other secrets.

Key takeaway

Logs provide a historical record that helps developers understand what an application was doing.


Chapter 38 — Configuration and Secrets

Question

Given below is a code snippet that:

  • Reads configuration from an environment variable.

  • Avoids hardcoding a database password.

  • Demonstrates externalized configuration.

What should be the output of the following code?

public class ConfigurationExample {

    public static void main(String[] args) {

        // Read the database URL from the environment.
        String databaseUrl =
                System.getenv("DATABASE_URL");

        // Use a safe fallback only for demonstration.
        if (databaseUrl == null) {
            databaseUrl = "not-configured";
        }

        // Display the configuration status.
        System.out.println(databaseUrl);
    }
}

Answer

In an environment where DATABASE_URL is not configured:

not-configured

If it is configured, Java prints its configured value.

Step-by-step explanation

  1. System.getenv() asks the operating system for an environment variable.

  2. The application does not need to hardcode the database URL.

  3. If the variable doesn't exist, Java returns null.

  4. The program substitutes "not-configured".

Key takeaway

Production configuration and secrets should come from the environment or a secure configuration system rather than being hardcoded into source code.


Chapter 39 — Docker

Question

Given below is a code snippet that:

  • Represents an application running inside a container conceptually.

  • Demonstrates the application itself does not need to know that Docker is managing its process.

  • Shows environment-based configuration.

What should be the output of the following code?

public class DockerApplication {

    public static void main(String[] args) {

        // Read configuration supplied by the container environment.
        String environment =
                System.getenv("APP_ENV");

        // Use a development fallback for demonstration.
        if (environment == null) {
            environment = "development";
        }

        // The Java application behaves according to its environment.
        System.out.println("Running in: " + environment);
    }
}

Answer

If APP_ENV=production is supplied:

Running in: production

Step-by-step explanation

  1. Docker can supply environment variables to a container.

  2. Java reads APP_ENV.

  3. The application does not need the production value hardcoded.

  4. The same application image can therefore be configured differently in different environments.

Key takeaway

Containers package applications consistently while environment-specific configuration remains external.


Chapter 40 — Docker Compose

Question

Given below is a code snippet that:

  • Represents a backend application communicating with a database.

  • Demonstrates service separation conceptually.

  • Shows that the application depends on another service.

What should be the output of the following code?

public class ServiceCommunication {

    public static void main(String[] args) {

        // In Docker Compose, this could represent a database service name.
        String databaseHost = "database";

        // The application constructs its database endpoint.
        String connectionString =
                "jdbc:postgresql://" + databaseHost + ":5432/app";

        // Display the generated endpoint.
        System.out.println(connectionString);
    }
}

Answer

jdbc:postgresql://database:5432/app

Step-by-step explanation

  1. "database" represents the database service name.

  2. Port 5432 is PostgreSQL's standard port.

  3. /app represents the database name.

  4. Java combines them into a JDBC connection string.

Key takeaway

Docker Compose can run multiple related services together and provide predictable service-to-service networking.


Chapter 41 — CI/CD

Question

Given below is a code snippet that:

  • Represents a simplified CI/CD pipeline.

  • Demonstrates the order of validation, testing, and deployment.

  • Shows why deployment should depend on successful tests.

What should be the output of the following code?

public class Pipeline {

    public static void main(String[] args) {

        // Step 1: build the application.
        boolean buildSuccessful = true;

        if (!buildSuccessful) {
            System.out.println("Pipeline stopped");
            return;
        }

        // Step 2: run automated tests.
        boolean testsSuccessful = true;

        if (!testsSuccessful) {
            System.out.println("Pipeline stopped");
            return;
        }

        // Step 3: deploy only after validation succeeds.
        System.out.println("Deploying application");
    }
}

Answer

Deploying application

Step-by-step explanation

  1. The build succeeds.

  2. The pipeline continues.

  3. Tests succeed.

  4. The pipeline continues.

  5. Deployment occurs.

Key takeaway

CI/CD automates the path from code changes through validation to deployment.


Chapter 42 — Monitoring and Health Checks

Question

Given below is a code snippet that:

  • Represents a health-check endpoint conceptually.

  • Checks whether required dependencies are available.

  • Demonstrates the difference between a running process and a healthy application.

What should be the output of the following code?

public class HealthCheck {

    public static void main(String[] args) {

        // Represent the state of the database.
        boolean databaseAvailable = true;

        // Represent the state of the application itself.
        boolean applicationRunning = true;

        // The application is healthy only when both are available.
        boolean healthy =
                applicationRunning && databaseAvailable;

        // Report the health status.
        System.out.println(healthy ? "UP" : "DOWN");
    }
}

Answer

UP

Step-by-step explanation

  1. The application is running.

  2. The database is available.

  3. && means both conditions must be true.

  4. Therefore healthy becomes true.

  5. The program prints UP.

Key takeaway

Health checks tell infrastructure whether an application is actually capable of operating, not merely whether its process exists.


Chapter 43 — Observability

Question

Given below is a code snippet that:

  • Demonstrates logs, metrics, and tracing conceptually.

  • Shows three different ways of understanding application behavior.

  • Represents observability data generated for one request.

What should be the output of the following code?

public class ObservabilityExample {

    public static void main(String[] args) {

        // Identify one request so related events can be connected.
        String requestId = "REQ-101";

        // Record a log event.
        System.out.println("LOG [" + requestId + "] Order received");

        // Record a metric.
        int ordersProcessed = 1;
        System.out.println("METRIC orders.processed=" + ordersProcessed);

        // Record a trace event.
        System.out.println("TRACE [" + requestId + "] payment-service");
    }
}

Answer

LOG [REQ-101] Order received
METRIC orders.processed=1
TRACE [REQ-101] payment-service

Step-by-step explanation

  1. The request gets an identifier.

  2. A log records what happened.

  3. A metric records a measurable value.

  4. A trace connects the request to a downstream service.

  5. Together, these provide a much clearer picture of system behavior.

Key takeaway

Observability combines logs, metrics, and traces to help engineers understand production systems.


Chapter 44 — Production Error Handling

Question

Given below is a code snippet that:

  • Separates internal errors from user-facing messages.

  • Uses an exception for an unexpected failure.

  • Demonstrates safe error handling.

What should be the output of the following code?

public class ProductionErrorHandling {

    public static void main(String[] args) {

        try {

            // Perform an operation that may fail.
            processPayment();

        } catch (PaymentException exception) {

            // Log the technical problem internally.
            System.out.println("LOG: Payment processing failed");

            // Give the user a safe message.
            System.out.println("Payment could not be completed");
        }
    }

    static void processPayment() {

        // Simulate a payment failure.
        throw new PaymentException("Database connection failed");
    }
}

class PaymentException extends RuntimeException {

    PaymentException(String message) {
        super(message);
    }
}

Answer

LOG: Payment processing failed
Payment could not be completed

Step-by-step explanation

  1. processPayment() throws a PaymentException.

  2. Java immediately leaves the method.

  3. The matching catch block receives the exception.

  4. The application records a safe internal log message.

  5. The user receives a generic message rather than an internal database error.

Beginner trap

Don't expose stack traces, database connection details, internal class names, or secrets directly to API users.

Key takeaway

Production error handling should give developers enough information to diagnose problems while giving users safe, useful responses.


Phase 7 — What You Should Now Understand

After completing this phase, you should be able to reason about a Java application at a much more professional level.

Code quality

You now have the foundation for:

Clean Code
Naming
Small focused methods
DRY
KISS
Composition

SOLID

You should understand:

S — Single Responsibility
O — Open/Closed
L — Liskov Substitution
I — Interface Segregation
D — Dependency Inversion

The important thing is not memorizing the five names. You should be able to look at code and ask:

"Is this class doing too much?"

"Will adding a new behavior require modifying stable code?"

"Can this implementation actually substitute for the abstraction?"

"Is this interface forcing clients to implement things they don't need?"

"Why does this class depend directly on this concrete implementation?"


Design patterns

You now have the conceptual foundation for:

Factory
Builder
Strategy
Adapter
Observer
Template Method

And, more importantly, you should understand why they exist.


Testing

You should now understand the progression:

Manual testing
      ↓
Automated tests
      ↓
JUnit
      ↓
Assertions
      ↓
Parameterized tests
      ↓
Mocks
      ↓
Unit tests
      ↓
Integration tests
      ↓
Testcontainers

Security

The core mental model is:

Request
   ↓
Authentication
   ↓
Authorization
   ↓
Application logic
   ↓
Response

And you should distinguish:

Authentication → Who are you?

Authorization → What are you allowed to do?

CORS → Which browser origins can interact?

CSRF → Can an attacker trick a browser into making an unwanted authenticated request?

JWT → A commonly used token format for carrying signed claims

OAuth2 → A framework/protocol family for delegated authorization

Production engineering

The final mental model is:

Code
 ↓
Git
 ↓
Tests
 ↓
Build
 ↓
Docker
 ↓
CI/CD
 ↓
Deployment
 ↓
Health checks
 ↓
Logs + Metrics + Traces
 ↓
Monitoring
 ↓
Debugging
 ↓
Reliable production system

The biggest Phase 7 takeaway

Professional Java development isn't just writing Java code. It is designing maintainable code, testing it, securing it, debugging it, deploying it, and operating it reliably in the real world.

Phase 8 is where we take all of this and move into advanced backend architecture, scalability, distributed systems, system design, real-world projects, and ultimately Java/backend interviews and remote-job readiness.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.