Java for Beginners: Write Your First Program (Without the Enterprise Trauma)
Learn Java from scratch. Variables, OOP, methods, arrays, ArrayList, inheritance, and common gotchas explained with clear examples and zero jargon.
Java has a reputation. Verbose. Enterprise-y. The language of 500-line XML configs and AbstractSingletonProxyFactoryBean. That reputation isn't entirely undeserved, but it misses the bigger picture.
Java runs on over 3 billion devices. It powers Android apps, backend services at Netflix and LinkedIn, trading systems at banks, and Minecraft. It's the most-taught language in university CS programs. It's fast, it's mature, and jobs are everywhere.
The verbosity is real, but modern Java (version 17+) has gotten significantly leaner. And the upside of all that structure is that once you learn it, you understand concepts that transfer to almost every other object-oriented language.
Let's write some Java.
Installing Java
You need the JDK (Java Development Kit). Download it from adoptium.net -- it's free and open source. Get the latest LTS version (Java 21 as of this writing).
Verify the installation:
java --version
javac --version
java runs programs. javac compiles them. Java is a compiled language -- you write .java files, compile them to .class bytecode, and the JVM (Java Virtual Machine) runs that bytecode. This is why Java is "write once, run anywhere."
For an editor, VS Code with the "Extension Pack for Java" works great. IntelliJ IDEA Community Edition is the gold standard if you want a full IDE.
Hello World
Create a file called HelloWorld.java:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Compile and run:
javac HelloWorld.java
java HelloWorld
Let's dissect every piece of this because nothing here is optional:
public class HelloWorld-- Java requires every file to have a class, and the filename must match the class name.HelloWorld.javacontainsclass HelloWorld.public static void main(String[] args)-- the entry point.publicmeans accessible from outside,staticmeans it belongs to the class (not an instance),voidmeans it returns nothing,String[] argsaccepts command-line arguments.System.out.println()-- prints text followed by a newline.System.out.print()skips the newline.
print("Hello"). You get used to it.
Variables and Data Types
Java is statically typed. Every variable needs a declared type:
public class Variables {
public static void main(String[] args) {
// Primitive types
int age = 25; // Whole numbers
double price = 19.99; // Decimal numbers
boolean isActive = true; // true or false
char grade = 'A'; // Single character (single quotes)
// Reference type
String name = "Alice"; // Text (double quotes, capital S)
// Type inference (Java 10+)
var count = 42; // Compiler infers int
var message = "Hello"; // Compiler infers String
System.out.println(name + " is " + age + " years old.");
System.out.println("Price: $" + price);
}
}
Key primitive types:
| Type | Size | Range / Purpose |
|---|---|---|
int | 32 bits | -2.1 billion to 2.1 billion |
long | 64 bits | Very large whole numbers |
double | 64 bits | Decimal numbers |
float | 32 bits | Smaller decimals (rarely used) |
boolean | 1 bit | true or false |
char | 16 bits | Single Unicode character |
String is not a primitive -- it's a class. That distinction matters later.
Constants
Use final for values that shouldn't change:
final double TAX_RATE = 0.08;
final String COMPANY = "CodeUp";
// TAX_RATE = 0.10; // Compile error
Conditionals
if / else
int score = 85;
if (score >= 90) {
System.out.println("Grade: A");
} else if (score >= 80) {
System.out.println("Grade: B");
} else if (score >= 70) {
System.out.println("Grade: C");
} else {
System.out.println("Grade: F");
}
switch
String day = "Wednesday";
switch (day) {
case "Monday":
case "Tuesday":
case "Wednesday":
case "Thursday":
case "Friday":
System.out.println("Weekday");
break;
case "Saturday":
case "Sunday":
System.out.println("Weekend");
break;
default:
System.out.println("Invalid day");
}
Modern Java (14+) has a cleaner switch expression:
String type = switch (day) {
case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" -> "Weekday";
case "Saturday", "Sunday" -> "Weekend";
default -> "Invalid";
};
Loops
for loop
for (int i = 0; i < 5; i++) {
System.out.println("Iteration: " + i);
}
while loop
int countdown = 5;
while (countdown > 0) {
System.out.println(countdown);
countdown--;
}
do-while loop
Runs at least once, checks the condition after:
int attempts = 0;
do {
System.out.println("Attempt " + (attempts + 1));
attempts++;
} while (attempts < 3);
Enhanced for loop (for-each)
String[] fruits = {"Apple", "Banana", "Cherry"};
for (String fruit : fruits) {
System.out.println(fruit);
}
Methods
Methods are reusable blocks of code:
public class Calculator {
// Method that returns a value
static int add(int a, int b) {
return a + b;
}
// Method that returns nothing
static void greet(String name) {
System.out.println("Hello, " + name + "!");
}
// Method with a default-like pattern using overloading
static double calculateTax(double amount) {
return calculateTax(amount, 0.08);
}
static double calculateTax(double amount, double rate) {
return amount * rate;
}
public static void main(String[] args) {
int result = add(10, 20);
System.out.println("Sum: " + result);
greet("Alice");
System.out.println("Tax: $" + calculateTax(100.00));
System.out.println("Tax: $" + calculateTax(100.00, 0.10));
}
}
Method overloading (same name, different parameters) is Java's way of providing flexibility without default arguments.
Arrays
Fixed-size collections:
// Declare and initialize
int[] numbers = {10, 20, 30, 40, 50};
String[] names = new String[3]; // Empty array of size 3
names[0] = "Alice";
names[1] = "Bob";
names[2] = "Carol";
// Access elements
System.out.println(numbers[0]); // 10
System.out.println(numbers.length); // 5
// Loop through
for (int i = 0; i < numbers.length; i++) {
System.out.println(numbers[i]);
}
// Or use for-each
for (int num : numbers) {
System.out.println(num);
}
Arrays have a fixed size. Once created, you can't add or remove elements. For that, you need ArrayList.
ArrayList: Dynamic Arrays
import java.util.ArrayList;
public class ShoppingList {
public static void main(String[] args) {
ArrayList<String> items = new ArrayList<>();
// Add items
items.add("Milk");
items.add("Bread");
items.add("Eggs");
// Access by index
System.out.println("First item: " + items.get(0));
// Size
System.out.println("Total items: " + items.size());
// Remove
items.remove("Bread");
// or by index: items.remove(1);
// Check if something exists
if (items.contains("Milk")) {
System.out.println("Don't forget the milk!");
}
// Loop
for (String item : items) {
System.out.println("- " + item);
}
}
}
Notice ArrayList -- the angle brackets specify what type the list holds. This is called generics. You can have ArrayList, ArrayList, etc. (Note: use Integer not int -- ArrayList requires wrapper classes for primitives.)
Object-Oriented Programming
This is where Java shines. OOP organizes code into classes (blueprints) and objects (instances of those blueprints).
Classes and Objects
public class Book {
// Fields (instance variables)
String title;
String author;
double price;
int pages;
// Constructor
public Book(String title, String author, double price, int pages) {
this.title = title;
this.author = author;
this.price = price;
this.pages = pages;
}
// Method
public String describe() {
return title + " by " + author + " ($" + price + ")";
}
public boolean isLong() {
return pages > 400;
}
}
Using it:
public class Bookstore {
public static void main(String[] args) {
Book book1 = new Book("Dune", "Frank Herbert", 9.99, 688);
Book book2 = new Book("1984", "George Orwell", 8.99, 328);
System.out.println(book1.describe());
System.out.println("Is it long? " + book1.isLong());
System.out.println(book2.describe());
System.out.println("Is it long? " + book2.isLong());
}
}
this refers to the current object. In the constructor, this.title = title distinguishes the field from the parameter.
Encapsulation
In practice, you make fields private and provide getters and setters:
public class BankAccount {
private String owner;
private double balance;
public BankAccount(String owner, double initialBalance) {
this.owner = owner;
this.balance = initialBalance;
}
public double getBalance() {
return balance;
}
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
public boolean withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return true;
}
return false;
}
}
The private keyword prevents outside code from directly changing balance. All modifications go through methods that enforce rules (no negative deposits, no overdrafts).
Inheritance
Classes can extend other classes to inherit their fields and methods:
public class Product {
protected String name;
protected double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
public String getInfo() {
return name + " - $" + price;
}
}
public class DigitalProduct extends Product {
private double fileSizeMB;
public DigitalProduct(String name, double price, double fileSizeMB) {
super(name, price); // Call parent constructor
this.fileSizeMB = fileSizeMB;
}
@Override
public String getInfo() {
return super.getInfo() + " (" + fileSizeMB + " MB download)";
}
}
extends creates an "is-a" relationship: a DigitalProduct is a Product. super() calls the parent constructor. @Override tells the compiler you're intentionally replacing a parent method.
Interfaces
Interfaces define a contract -- what methods a class must implement, without specifying how:
public interface Searchable {
boolean matches(String query);
}
public interface Printable {
String toPrintFormat();
}
public class Article implements Searchable, Printable {
private String title;
private String content;
public Article(String title, String content) {
this.title = title;
this.content = content;
}
@Override
public boolean matches(String query) {
return title.toLowerCase().contains(query.toLowerCase())
|| content.toLowerCase().contains(query.toLowerCase());
}
@Override
public String toPrintFormat() {
return "=== " + title + " ===\n" + content;
}
}
A class can implement multiple interfaces but can only extend one class. Interfaces are how Java achieves a form of multiple inheritance without the complexity.
Building Something: A Simple Task Manager
Let's combine everything into a working program:
import java.util.ArrayList;
import java.util.Scanner;
public class TaskManager {
private ArrayList<String> tasks = new ArrayList<>();
private ArrayList<Boolean> completed = new ArrayList<>();
public void addTask(String task) {
tasks.add(task);
completed.add(false);
System.out.println("Added: " + task);
}
public void completeTask(int index) {
if (index >= 0 && index < tasks.size()) {
completed.set(index, true);
System.out.println("Completed: " + tasks.get(index));
} else {
System.out.println("Invalid task number.");
}
}
public void showTasks() {
if (tasks.isEmpty()) {
System.out.println("No tasks yet.");
return;
}
for (int i = 0; i < tasks.size(); i++) {
String status = completed.get(i) ? "[x]" : "[ ]";
System.out.println((i + 1) + ". " + status + " " + tasks.get(i));
}
}
public void showStats() {
long done = completed.stream().filter(c -> c).count();
System.out.println("Total: " + tasks.size()
+ " | Done: " + done
+ " | Remaining: " + (tasks.size() - done));
}
public static void main(String[] args) {
TaskManager manager = new TaskManager();
Scanner scanner = new Scanner(System.in);
System.out.println("Task Manager (type 'help' for commands)");
while (true) {
System.out.print("> ");
String input = scanner.nextLine().trim();
if (input.equals("quit")) {
break;
} else if (input.equals("help")) {
System.out.println("Commands: add <task>, done <number>, list, stats, quit");
} else if (input.startsWith("add ")) {
manager.addTask(input.substring(4));
} else if (input.startsWith("done ")) {
int num = Integer.parseInt(input.substring(5)) - 1;
manager.completeTask(num);
} else if (input.equals("list")) {
manager.showTasks();
} else if (input.equals("stats")) {
manager.showStats();
} else {
System.out.println("Unknown command. Type 'help'.");
}
}
scanner.close();
System.out.println("Goodbye!");
}
}
Compile and run it: javac TaskManager.java && java TaskManager. You've got a working interactive program with state management, user input, and multiple methods working together.
Common Gotchas
== vs .equals()
This trips up every Java beginner:
String a = new String("hello");
String b = new String("hello");
System.out.println(a == b); // false (compares references)
System.out.println(a.equals(b)); // true (compares values)
== checks if two variables point to the same object in memory. .equals() checks if their values are the same. For strings and other objects, always use .equals().
There's a subtle catch: string literals are cached (interned), so "hello" == "hello" is true. But don't rely on this -- use .equals() every time.
NullPointerException
The most common Java runtime error:
String name = null;
System.out.println(name.length()); // NullPointerException!
You're calling a method on something that doesn't exist. Always check for null before using an object:
if (name != null) {
System.out.println(name.length());
}
Or in modern Java, use Optional:
import java.util.Optional;
Optional<String> maybeName = Optional.ofNullable(getName());
maybeName.ifPresent(n -> System.out.println(n.length()));
Array Index Out of Bounds
int[] nums = {1, 2, 3};
System.out.println(nums[3]); // ArrayIndexOutOfBoundsException
Arrays are zero-indexed. An array of size 3 has indices 0, 1, and 2. Index 3 doesn't exist. Use .length to stay in bounds.
Forgetting break in switch
switch (value) {
case 1:
System.out.println("One");
// Missing break! Falls through to case 2
case 2:
System.out.println("Two");
break;
}
Without break, execution falls through to the next case. Use the modern switch expression (with ->) to avoid this entirely.
What to Learn Next
You've covered the fundamentals. Here's the roadmap:
- Collections framework -- HashMap, HashSet, LinkedList, and when to use each
- Exception handling -- try/catch/finally, creating custom exceptions
- File I/O -- reading and writing files
- Streams API -- functional-style operations on collections (filter, map, reduce)
- Multithreading -- running code in parallel
- Build tools -- Maven or Gradle for managing dependencies
- Spring Boot -- if you're heading toward backend development
Explore more tutorials and guides at CodeUp.