A Beginner's Journey into Java: Writing Your First Real-World Program

Why Learn Java?

Java stands as one of the most powerful and enduring programming languages, renowned for its simplicity, robustness, and versatility. Over the years, it has carved a niche across diverse domains, ranging from mobile and web applications to enterprise-level systems, scientific computing, and even embedded devices. Its enduring popularity is a testament to its adaptability and relevance, making it an indispensable skill for students and professionals alike.

One of Java's most remarkable traits is its platform independence, encapsulated in the philosophy of "Write Once, Run Anywhere" (WORA). Programs written in Java can seamlessly run on any platform equipped with a Java Virtual Machine (JVM), sparing developers the hassle of dealing with hardware-specific constraints. This feature has not only widened its applicability but also contributed to its dominance in industry and academia.

For students beginning their programming journey, Java offers an approachable yet powerful starting point. It provides the right balance between simplicity and structure, making it ideal for learning foundational programming concepts such as object-oriented design, data structures, and algorithms. As you grow in skill and ambition, Java supports you in transitioning from writing simple console-based programs to developing full-fledged applications used by millions.

The motivation to learn Java lies not just in its capabilities but also in the doors it opens. Mastery of Java equips you with skills to build dynamic websites, responsive mobile applications, robust back-end systems, and scalable enterprise solutions. From writing your first "Hello, World!" program to architecting complex software, Java's ecosystem offers the tools and opportunities to evolve into a skilled developer capable of addressing real-world challenges.

Your journey into Java starts here, and the possibilities are endless. Let’s embark on this path together and see how a simple yet elegant programming language can become the foundation of your future in technology.

The Java Main Method and the Main Class: The Heart of a Java Program

Every Java program begins its journey with the main method, which serves as the entry point for execution. The main method's unique signature allows the Java Virtual Machine (JVM) to identify and start your program. The main method resides within a main class, which is the Java class containing this crucial method.

The Main Class: Home for the Main Method

In Java, all code exists within classes. The main class is the class containing the main method. This class typically serves as the launching pad for the program. While a Java application can contain many classes, only the main class must include the main method.

Here’s an example of a simple main class:

public class MainExample {
    public static void main(String[] args) {
        System.out.println("Welcome to Java Programming!");

        // Display command-line arguments if provided
        if (args.length > 0) {
            System.out.println("Command-line arguments:");
            for (String arg : args) {
                System.out.println(arg);
            }
        }
    }
}

This class includes:

  • Class Declaration: The class is named MainExample to reflect its purpose.

  • Main Method: This method starts the program and contains instructions for execution.

Compiling and Running Java Code with javac and java

  1. Compiling Java Code Using javac
    The javac command compiles Java source code (.java files) into bytecode (.class files) that the JVM can execute.

    Steps to compile the above example:

    • Save the code in a file named MainExample.java. The file name must match the class name containing the main method.

    • Open a terminal or command prompt and navigate to the directory containing the file.

    • Compile the file with the following command:

        javac MainExample.java
      
    • If the code has no errors, this creates a file named MainExample.class.

  2. Running the Compiled Program Using java
    Once compiled, you can execute the program using the java command, specifying the class name without the .class extension:

     java MainExample
    

    Example outputs:

    • If you run:

        java MainExample
      

      The output will be:

        Welcome to Java Programming!
      
    • If you pass arguments:

        java MainExample Hello Java
      

      The output will be:

        Welcome to Java Programming!
        Command-line arguments:
        Hello
        Java
      

Understanding Compilation and Execution Process

  • Compilation with javac:

    • Translates human-readable Java code into bytecode.

    • Creates .class files, each representing a compiled class.

  • Execution with java:

    • The JVM loads the .class file and starts executing the main method.

Key Points

  • The main class must be public and should match the file name.

  • The main method is where execution begins.

  • Command-line arguments passed during execution are available as elements in the String[] args array.

Understanding Packages: Organizing Your Code

In Java, packages are containers used to organize classes and interfaces into a structured hierarchy. They play a crucial role in keeping code modular, readable, and manageable as your projects grow. Much like folders on a computer, packages help group related files, making it easier to locate and work with them.

What is a Package?

A package in Java is a namespace that organizes classes and interfaces. It’s essentially a way to group related classes to:

  • Avoid naming conflicts.

  • Enhance code readability and maintainability.

  • Control access to classes and interfaces using access modifiers.

For example, the java.util package contains utility classes like ArrayList and HashMap, while the java.io package deals with input and output operations.

The package Keyword

The package keyword defines the package to which a class belongs. This declaration must be the first statement in a Java file (except for comments).

Example:

package com.example.library;

public class Book {
    private String title;
    private String author;

    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

    public void displayInfo() {
        System.out.println("Title: " + title + ", Author: " + author);
    }
}

Here:

  • The class Book belongs to the package com.example.library.

  • The package name reflects a hierarchical structure (like nested folders).

Real-World Analogy: Packages as Folders

Think of your computer's file system. Files are stored in folders and subfolders to keep things organized. Similarly:

  • A package is like a folder.

  • Classes and interfaces are like files inside the folder.

  • A fully qualified class name (e.g., com.example.library.Book) includes the package path, just like a file path on your computer.

Benefits of Using Packages

  1. Code Organization: Packages group related classes logically, making your codebase easier to navigate.

  2. Avoiding Naming Conflicts: Packages create unique namespaces. For example, two developers can create a class named Book, but as long as they are in different packages, there is no conflict (e.g., com.library.Book vs. com.store.Book).

  3. Better Maintainability: Packages encourage modularity, making it easier to manage and modify parts of your program without affecting unrelated components.

  4. Access Control: Using access modifiers (e.g., public, protected, default), you can restrict or expose access to classes and methods within or outside a package.

Default Package vs. Custom Packages

  • Default Package:
    If you do not specify a package, your class belongs to the default package. While this is fine for small projects or learning, it’s not suitable for larger projects due to a lack of organization and potential naming conflicts.

      public class DefaultExample {
          public static void main(String[] args) {
              System.out.println("This class belongs to the default package.");
          }
      }
    

    Compile and run directly:

      javac DefaultExample.java
      java DefaultExample
    
  • Custom Packages:
    For larger projects, create custom packages to organize classes. For instance, in a library management system:

      src/
        com/
          example/
            library/
              Book.java
              Library.java
    

    Compilation:

      javac -d . src/com/example/library/Book.java src/com/example/library/Library.java
    

    Execution:

      java com.example.library.Library
    

Creating a Multi-Class, Multi-Package Java Program

Let’s build a small library management system in Java, with a focus on organizing the code using multiple classes and packages. This example will illustrate how separating classes into logical packages improves clarity and maintainability.

Scenario Overview

The library management system will include:

  • A Library class to manage operations (core logic).

  • A Book class to represent books (data model).

  • A Member class to represent library members (data model).

These classes will be divided into the following packages:

  • library: Contains the core logic class Library.

  • models: Contains data model classes Book and Member.

Directory Structure

We’ll organize the files into a directory structure like this:

src/
  library/
    Library.java
  models/
    Book.java
    Member.java

Code Walkthrough

1. Book.java (models package)
Defines the attributes and behavior of a book.

package models;

public class Book {
    private String title;
    private String author;

    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

    public String getTitle() {
        return title;
    }

    public String getAuthor() {
        return author;
    }

    @Override
    public String toString() {
        return "Book{" + "title='" + title + '\'' + ", author='" + author + '\'' + '}';
    }
}

2. Member.java (models package)
Represents a library member.

package models;

public class Member {
    private String name;
    private int memberId;

    public Member(String name, int memberId) {
        this.name = name;
        this.memberId = memberId;
    }

    public String getName() {
        return name;
    }

    public int getMemberId() {
        return memberId;
    }

    @Override
    public String toString() {
        return "Member{" + "name='" + name + '\'' + ", memberId=" + memberId + '}';
    }
}

3. Library.java (library package)
Implements the core library functionality.

package library;

import models.Book;
import models.Member;
import java.util.ArrayList;
import java.util.List;

public class Library {
    private List<Book> books = new ArrayList<>();
    private List<Member> members = new ArrayList<>();

    public void addBook(Book book) {
        books.add(book);
        System.out.println("Added book: " + book);
    }

    public void addMember(Member member) {
        members.add(member);
        System.out.println("Added member: " + member);
    }

    public void listBooks() {
        System.out.println("Books in library:");
        for (Book book : books) {
            System.out.println(book);
        }
    }

    public void listMembers() {
        System.out.println("Library members:");
        for (Member member : members) {
            System.out.println(member);
        }
    }

    public static void main(String[] args) {
        Library library = new Library();

        Book book1 = new Book("1984", "George Orwell");
        Book book2 = new Book("To Kill a Mockingbird", "Harper Lee");

        Member member1 = new Member("Alice", 1);
        Member member2 = new Member("Bob", 2);

        library.addBook(book1);
        library.addBook(book2);

        library.addMember(member1);
        library.addMember(member2);

        library.listBooks();
        library.listMembers();
    }
}

Compiling and Running the Program

  1. Compile All Classes Use the javac command to compile all .java files, specifying the source directory and the output directory for compiled files:

     javac -d . src/models/Book.java src/models/Member.java src/library/Library.java
    

    The -d . flag ensures that the compiled .class files are placed in the appropriate package structure.

    After compilation, your directory structure will look like this:

     library/
       Library.class
     models/
       Book.class
       Member.class
    
  2. Run the Program Run the Library class using the java command, specifying the fully qualified class name:

     java library.Library
    

Output

The program will display:

Added book: Book{title='1984', author='George Orwell'}
Added book: Book{title='To Kill a Mockingbird', author='Harper Lee'}
Added member: Member{name='Alice', memberId=1}
Added member: Member{name='Bob', memberId=2}
Books in library:
Book{title='1984', author='George Orwell'}
Book{title='To Kill a Mockingbird', author='Harper Lee'}
Library members:
Member{name='Alice', memberId=1}
Member{name='Bob', memberId=2}

Why This Organization is Helpful

  1. Code Organization: Logical separation of classes ensures that each part of the program is easy to find and understand.

  2. Scalability: Adding new features, such as Loan or Catalog, is straightforward without disrupting the existing structure.

  3. Reusability: Data models like Book and Member can be reused in other systems or applications.

  4. Avoiding Conflicts: Using packages prevents naming conflicts across large projects.

Compiling Java Code with javac

The javac compiler is a fundamental part of the Java Development Kit (JDK). Its primary role is to convert human-readable Java source files (.java) into bytecode (.class), a platform-independent intermediate representation that the Java Virtual Machine (JVM) can execute.

How javac Works

  1. Reads the .java file(s) containing Java code.

  2. Validates the code against Java’s syntax and semantic rules.

  3. Generates .class files containing bytecode, which can be run by the JVM.

Compiling a Multi-Package Program

Consider the library management system with the following structure:

src/
  library/
    Library.java
  models/
    Book.java
    Member.java

To compile the entire program:

javac -d . src/library/Library.java src/models/Book.java src/models/Member.java

Here’s what the command does:

  • -d .: The -d flag specifies the root directory where compiled .class files will be placed.

    • If omitted, all .class files are created in the current directory, ignoring their package hierarchy.

    • With -d ., the javac compiler generates subdirectories matching the package structure (library and models).

  • src/library/Library.java and other arguments: Specify the source files to compile.

After compilation, the directory structure looks like this:

library/
  Library.class
models/
  Book.class
  Member.class

Running Java Programs with java

The java command invokes the JVM to execute compiled bytecode. The JVM:

  1. Loads the .class file into memory.

  2. Locates the main method in the specified class.

  3. Begins executing the instructions defined in the main method.

Executing a Multi-Package Program

To run the program:

java library.Library

Here’s what happens:

  • library.Library: Specifies the fully qualified class name of the Library class:

    • library: The package containing the class.

    • Library: The class name itself.

  • The JVM looks for the Library.class file in the library directory and executes its main method.

Importance of Package Names

The JVM uses package names to locate classes. If the compiled .class files are not in the correct directories matching their package structure, the JVM cannot find them.

For example, this will result in an error:

java Library

Output:

Error: Could not find or load main class Library

Troubleshooting Common Errors

  1. ClassNotFoundException

    • Cause: The JVM cannot locate the .class file corresponding to the specified fully qualified class name.

    • Solution: Ensure that:

      • The javac command was run with the -d flag to create the proper package structure.

      • The java command specifies the fully qualified class name.

  2. Incorrect Package Declaration

    • Cause: The package declaration in the .java file doesn’t match the directory structure.

    • Solution: Verify that the package name in the code matches the directory hierarchy.

  3. Missing main Method

    • Cause: The specified class doesn’t contain a main method with the signature:

        public static void main(String[] args)
      
    • Solution: Ensure the class has the correct main method.

Recap: Key Steps for Success

  1. Use javac -d . to compile all .java files while preserving the package structure.

  2. Run the program with java by specifying the fully qualified class name.

  3. Verify the package declarations, directory structure, and class paths to avoid common errors.

Creating a Multi-Class, Multi-Package Java Program with Two-Level Packages: A Banking System Example

For a fresh perspective, let’s build a simple banking system where we organize the project into two-level packages. This will demonstrate how to manage a hierarchical package structure and maintain clarity in large-scale projects.

Scenario Overview

We’ll implement a banking system with:

  1. Banking Operations: Handle transactions such as deposit and withdrawal.

  2. Data Models: Represent entities like Account and Customer.

Directory Structure with Two-Level Packages

The classes will be organized as follows:

src/
  banking/
    operations/
      Transaction.java
    models/
      Account.java
      Customer.java

Code Walkthrough

1. Account.java

package banking.models;

public class Account {
    private String accountNumber;
    private double balance;

    public Account(String accountNumber, double initialBalance) {
        this.accountNumber = accountNumber;
        this.balance = initialBalance;
    }

    public String getAccountNumber() {
        return accountNumber;
    }

    public double getBalance() {
        return balance;
    }

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            System.out.println("Deposited: " + amount + ", New Balance: " + balance);
        } else {
            System.out.println("Invalid deposit amount.");
        }
    }

    public boolean withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            System.out.println("Withdrew: " + amount + ", Remaining Balance: " + balance);
            return true;
        } else {
            System.out.println("Invalid withdrawal amount or insufficient funds.");
            return false;
        }
    }

    @Override
    public String toString() {
        return "Account{accountNumber='" + accountNumber + "', balance=" + balance + '}';
    }
}

2. Customer.java

package banking.models;

public class Customer {
    private String name;
    private String customerId;

    public Customer(String name, String customerId) {
        this.name = name;
        this.customerId = customerId;
    }

    public String getName() {
        return name;
    }

    public String getCustomerId() {
        return customerId;
    }

    @Override
    public String toString() {
        return "Customer{name='" + name + "', customerId='" + customerId + "'}";
    }
}

3. Transaction.java

package banking.operations;

import banking.models.Account;
import banking.models.Customer;

public class Transaction {
    public static void main(String[] args) {
        Customer customer = new Customer("John Doe", "C123");
        Account account = new Account("A456", 1000.00);

        System.out.println("Customer Details: " + customer);
        System.out.println("Account Details: " + account);

        account.deposit(500.00);  // Deposit money
        account.withdraw(300.00); // Withdraw money
        account.withdraw(1500.00); // Attempt to overdraw
    }
}

Step-by-Step Instructions

Step 1: Create the Project Folder Structure

  1. Open a terminal or file explorer.

  2. Create the following directory structure:

     src/
       banking/
         operations/
           Transaction.java
         models/
           Account.java
           Customer.java
    

Step 2: Write the Code

  1. Save each class in its respective folder, ensuring the file name matches the class name.

Step 3: Compile the Program

  1. Open the terminal and navigate to the src directory.

  2. Compile all the classes using the javac command:

     javac -d . banking/models/Account.java banking/models/Customer.java banking/operations/Transaction.java
    

    The -d . flag creates the appropriate package structure for the compiled files:

     banking/
       models/
         Account.class
         Customer.class
       operations/
         Transaction.class
    

Step 4: Run the Program Execute the Transaction class using the java command:

java banking.operations.Transaction

Expected Output

Customer Details: Customer{name='John Doe', customerId='C123'}
Account Details: Account{accountNumber='A456', balance=1000.0}
Deposited: 500.0, New Balance: 1500.0
Withdrew: 300.0, Remaining Balance: 1200.0
Invalid withdrawal amount or insufficient funds.

Tips and Best Practices for Beginners

  1. Organize Code Hierarchically: Use multiple levels in packages to reflect the functionality and relationships between components.

  2. Write Meaningful Names: Use clear and descriptive names for classes and packages.

  3. Incremental Compilation: Compile and test one package or class at a time to catch errors early.

0
Subscribe to my newsletter

Read articles from Jyotiprakash Mishra directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Jyotiprakash Mishra
Jyotiprakash Mishra

I am Jyotiprakash, a deeply driven computer systems engineer, software developer, teacher, and philosopher. With a decade of professional experience, I have contributed to various cutting-edge software products in network security, mobile apps, and healthcare software at renowned companies like Oracle, Yahoo, and Epic. My academic journey has taken me to prestigious institutions such as the University of Wisconsin-Madison and BITS Pilani in India, where I consistently ranked among the top of my class. At my core, I am a computer enthusiast with a profound interest in understanding the intricacies of computer programming. My skills are not limited to application programming in Java; I have also delved deeply into computer hardware, learning about various architectures, low-level assembly programming, Linux kernel implementation, and writing device drivers. The contributions of Linus Torvalds, Ken Thompson, and Dennis Ritchie—who revolutionized the computer industry—inspire me. I believe that real contributions to computer science are made by mastering all levels of abstraction and understanding systems inside out. In addition to my professional pursuits, I am passionate about teaching and sharing knowledge. I have spent two years as a teaching assistant at UW Madison, where I taught complex concepts in operating systems, computer graphics, and data structures to both graduate and undergraduate students. Currently, I am an assistant professor at KIIT, Bhubaneswar, where I continue to teach computer science to undergraduate and graduate students. I am also working on writing a few free books on systems programming, as I believe in freely sharing knowledge to empower others.