Language lab · Beginner → Enterprise
☕ Java
Concept → trace → write code → run tests → explain why → improve → mini-project. Start in the browser if you like; set up your own computer when you are ready.
📖 Topics with real code (16)
Every verified output below was produced by running the code with openjdk 21.0.13 2026-10-20 LTS on 5 Nov 2026. Open a topic to edit the code and run it on our isolated compile server (sign in needed).
🌱 Beginner (5)
Variables and data types
Java is statically typed: every variable has a fixed type. Primitives: int, long, double, boolean, char. String is a class. var lets the compiler infer the type. Casting converts between types.
public class Main {
public static void main(String[] args) {
int age = 15;
long population = 1_428_000_000L;
double height = 1.62;
boolean isStudent = true;
char grade = 'A';
String name = "Asha";
var city = "Hyderabad"; // inferred as String
System.out.println(name + " " + age + " " + height + " " + isStudent + " " + grade + " " + city);
System.out.println("population: " + population + ", int max: " + Integer.MAX_VALUE);
int marks = Integer.parseInt("88") + 2;
double avg = (double) 7 / 2; // cast before dividing
System.out.println(marks + " " + avg + " " + (7 / 2) + " " + (int) 9.99);
}
}
✅ Verified output
Asha 15 1.62 true A Hyderabad population: 1428000000, int max: 2147483647 90 3.5 3 9
Operators and boolean logic
Integer division drops the decimal part; % is the remainder. && and || short-circuit. == compares primitives; use .equals() for Strings.
public class Main {
public static void main(String[] args) {
int a = 17, b = 5;
System.out.println((a + b) + " " + (a - b) + " " + (a * b) + " " + (a / b) + " " + (a % b) + " " + Math.pow(a, 2));
boolean hasTicket = true, isVip = false;
System.out.println("enter: " + (hasTicket && !isVip) + " either: " + (hasTicket || isVip));
String s1 = "hello", s2 = new String("hello");
System.out.println("== " + (s1 == s2) + " equals " + s1.equals(s2));
int x = 5;
x += 3; x *= 2; x++;
System.out.println("x = " + x + ", bits 6&3=" + (6 & 3) + " 6|3=" + (6 | 3));
}
}
✅ Verified output
22 12 85 3 2 289.0 enter: true either: true == false equals true x = 17, bits 6&3=2 6|3=7
Conditionals: if, else, switch
if/else picks a branch. The modern switch expression (Java 14+) returns a value and needs no break.
public class Main {
public static void main(String[] args) {
int marks = 78;
String grade;
if (marks >= 90) grade = "A+";
else if (marks >= 75) grade = "A";
else if (marks >= 35) grade = "Pass";
else grade = "Fail";
System.out.println("Grade: " + grade);
String day = "SAT";
String kind = switch (day) {
case "SAT", "SUN" -> "Weekend";
default -> "School day";
};
System.out.println(kind + ", ternary: " + (marks >= 35 ? "passed" : "try again"));
}
}
✅ Verified output
Grade: A Weekend, ternary: passed
Loops: for, while, do-while, for-each
for counts, while checks first, do-while runs at least once, for-each walks arrays and lists. break and continue control the loop.
public class Main {
public static void main(String[] args) {
for (int i = 1; i <= 5; i++) {
if (i == 3) continue;
System.out.println("for " + i);
}
int n = 10;
while (n > 0) {
if (n < 4) break;
System.out.println("while " + n);
n -= 3;
}
int k = 100;
do { System.out.println("do-while runs once: " + k); } while (k < 0);
for (String s : new String[]{"red", "green"}) System.out.println("each " + s);
for (int r = 1; r <= 3; r++) {
StringBuilder row = new StringBuilder();
for (int c = 1; c <= 3; c++) row.append(String.format("%3d", r * c));
System.out.println(row);
}
}
}
✅ Verified output
for 1 for 2 for 4 for 5 while 10 while 7 while 4 do-while runs once: 100 each red each green 1 2 3 2 4 6 3 6 9
Methods, parameters and overloading
A method has a return type and parameters. Overloading gives several methods the same name with different parameters. Primitives are passed by value.
public class Main {
static int area(int length, int width) { return length * width; }
static int area(int side) { return area(side, side); } // overload
static double area(double radius, boolean circle) { return Math.PI * radius * radius; }
static void tryChange(int value) { value = 99; } // copy only
static double average(int... nums) { // varargs
int sum = 0;
for (int x : nums) sum += x;
return (double) sum / nums.length;
}
public static void main(String[] args) {
System.out.println(area(5, 3) + " " + area(4) + " " + String.format("%.2f", area(1.0, true)));
int v = 1;
tryChange(v);
System.out.println("still " + v + ", average " + average(4, 9, 2, 7));
}
}
✅ Verified output
15 16 3.14 still 1, average 5.5
🌿 Intermediate (7)
Arrays and ArrayList
Arrays have a fixed size; ArrayList grows. Arrays.sort and Collections.sort order them; streams can filter and map.
import java.util.*;
public class Main {
public static void main(String[] args) {
int[] fixed = {72, 95, 64, 88};
Arrays.sort(fixed);
System.out.println(Arrays.toString(fixed) + " length " + fixed.length);
List<Integer> marks = new ArrayList<>(List.of(72, 95, 64, 88));
marks.add(91);
Collections.sort(marks);
System.out.println(marks + " first " + marks.get(0) + " last " + marks.get(marks.size() - 1));
List<Integer> oddSquares = new ArrayList<>();
for (int x = 1; x <= 5; x++) if (x % 2 == 1) oddSquares.add(x * x);
System.out.println("odd squares " + oddSquares);
int[][] grid = {{1, 2}, {3, 4}};
System.out.println("2D: " + Arrays.deepToString(grid));
}
}
✅ Verified output
[64, 72, 88, 95] length 4 [64, 72, 88, 91, 95] first 64 last 95 odd squares [1, 9, 25] 2D: [[1, 2], [3, 4]]
Strings and StringBuilder
Strings are immutable; StringBuilder is fast for building text in a loop. String.format and text blocks help formatting.
public class Main {
public static void main(String[] args) {
String t = " GayaGnan learning ".strip();
System.out.println(t.toUpperCase() + " | " + t.replace("learning", "labs") + " | " + t.length());
String[] words = t.split(" ");
System.out.println(String.join("-", words) + " starts with Gaya: " + t.startsWith("Gaya"));
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= 5; i++) sb.append(i).append(i < 5 ? "," : "");
System.out.println(sb + " reversed " + new StringBuilder("level").reverse());
System.out.println(String.format("Total: %.2f for %d items", 49.5 * 3, 3));
String block = """
Line one
Line two""";
System.out.println(block);
}
}
✅ Verified output
GAYAGNAN LEARNING | GayaGnan labs | 17 GayaGnan-learning starts with Gaya: true 1,2,3,4,5 reversed level Total: 148.50 for 3 items Line one Line two
Maps and sets
HashMap stores key-value pairs; TreeMap keeps keys sorted; HashSet keeps unique items. merge() is handy for counting.
import java.util.*;
public class Main {
public static void main(String[] args) {
Map<String, Integer> stock = new TreeMap<>(Map.of("pen", 40, "book", 12));
stock.put("eraser", 25);
stock.put("pen", stock.get("pen") - 5);
stock.forEach((k, v) -> System.out.println(k + " " + v));
System.out.println("has ruler? " + stock.containsKey("ruler") + ", ruler or 0: " + stock.getOrDefault("ruler", 0));
Set<String> a = new TreeSet<>(Set.of("maths", "science", "art"));
Set<String> both = new TreeSet<>(a);
both.retainAll(Set.of("science", "music"));
System.out.println("both " + both);
Map<String, Integer> freq = new TreeMap<>();
for (String w : "the cat and the hat and the bat".split(" ")) freq.merge(w, 1, Integer::sum);
System.out.println(freq);
}
}
✅ Verified output
book 12
eraser 25
pen 35
has ruler? false, ruler or 0: 0
both [science]
{and=2, bat=1, cat=1, hat=1, the=3}
Classes, encapsulation and records
private fields with public methods protect an object's data. A record (Java 16+) is a short way to write an immutable data class.
public class Main {
static class BankAccount {
private final String owner;
private double balance;
BankAccount(String owner, double balance) { this.owner = owner; this.balance = balance; }
void deposit(double amount) {
if (amount <= 0) throw new IllegalArgumentException("amount must be positive");
balance += amount;
}
double getBalance() { return balance; }
String getOwner() { return owner; }
}
record Student(String name, int marks) {}
public static void main(String[] args) {
BankAccount acc = new BankAccount("Asha", 500);
acc.deposit(250);
System.out.println(acc.getOwner() + " " + acc.getBalance());
Student s = new Student("Ravi", 95);
System.out.println(s + " " + s.name() + " equal: " + s.equals(new Student("Ravi", 95)));
}
}
✅ Verified output
Asha 750.0 Student[name=Ravi, marks=95] Ravi equal: true
Inheritance, interfaces and polymorphism
An interface lists methods a class promises to provide; abstract classes share code. The same loop calls the right area() for each shape.
import java.util.List;
public class Main {
interface Shape { double area(); default String describe() { return getClass().getSimpleName() + " " + String.format("%.2f", area()); } }
static class Circle implements Shape { double r; Circle(double r) { this.r = r; } public double area() { return Math.PI * r * r; } }
static class Rect implements Shape { double w, h; Rect(double w, double h) { this.w = w; this.h = h; } public double area() { return w * h; } }
static abstract class Animal { abstract String sound(); String speak() { return "says " + sound(); } }
static class Dog extends Animal { String sound() { return "Woof"; } }
public static void main(String[] args) {
for (Shape s : List.of(new Circle(1), new Rect(3, 4))) System.out.println(s.describe());
Animal a = new Dog();
System.out.println("Dog " + a.speak() + ", is Animal: " + (a instanceof Animal));
}
}
✅ Verified output
Circle 3.14 Rect 12.00 Dog says Woof, is Animal: true
Exceptions
try/catch/finally handles errors. Checked exceptions must be declared or caught; custom exceptions extend Exception or RuntimeException. try-with-resources closes things automatically.
public class Main {
static class InsufficientFundsException extends Exception {
InsufficientFundsException(String m) { super(m); }
}
static double withdraw(double balance, double amount) throws InsufficientFundsException {
if (amount > balance) throw new InsufficientFundsException("need " + (amount - balance) + " more");
return balance - amount;
}
public static void main(String[] args) {
try {
System.out.println(10 / 0);
} catch (ArithmeticException e) {
System.out.println("ArithmeticException: " + e.getMessage());
} finally {
System.out.println("finally always runs");
}
try {
withdraw(100, 150);
} catch (InsufficientFundsException e) {
System.out.println("Custom: " + e.getMessage());
}
try { Integer.parseInt("abc"); } catch (NumberFormatException e) { System.out.println("Bad number"); }
}
}
✅ Verified output
ArithmeticException: / by zero finally always runs Custom: need 50.0 more Bad number
Files with java.nio
Files.writeString and Files.readAllLines handle text files. Paths are built with Path.of. The file goes to the temp folder and is deleted after.
import java.nio.file.*;
import java.util.List;
public class Main {
public static void main(String[] args) throws Exception {
Path file = Files.createTempFile("marks", ".csv");
Files.writeString(file, "name,marks\nAsha,88\nRavi,95\n");
List<String> lines = Files.readAllLines(file);
int total = 0;
for (String line : lines.subList(1, lines.size())) {
String[] parts = line.split(",");
System.out.println(parts[0] + " -> " + parts[1]);
total += Integer.parseInt(parts[1]);
}
System.out.println("average " + (double) total / (lines.size() - 1));
Files.delete(file);
System.out.println("deleted: " + !Files.exists(file));
}
}
✅ Verified output
Asha -> 88 Ravi -> 95 average 91.5 deleted: true
🌳 Advanced (4)
Recursion and memoization
A recursive method calls itself with a smaller input. A HashMap cache turns the slow fib into a fast one; long holds fib(80).
import java.util.*;
public class Main {
static Map<Integer, Long> memo = new HashMap<>();
static long fib(int n) {
if (n < 2) return n;
if (memo.containsKey(n)) return memo.get(n);
long v = fib(n - 1) + fib(n - 2);
memo.put(n, v);
return v;
}
static int binarySearch(int[] a, int target, int lo, int hi) {
if (lo > hi) return -1;
int mid = (lo + hi) >>> 1;
if (a[mid] == target) return mid;
return a[mid] < target ? binarySearch(a, target, mid + 1, hi) : binarySearch(a, target, lo, mid - 1);
}
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) sb.append(fib(i)).append(' ');
System.out.println(sb + "| fib(80) = " + fib(80));
int[] sorted = {2, 5, 8, 12, 16, 23, 38};
System.out.println("index of 23: " + binarySearch(sorted, 23, 0, sorted.length - 1));
}
}
✅ Verified output
0 1 1 2 3 5 8 13 21 34 | fib(80) = 23416728348467685 index of 23: 5
Generics, lambdas and streams
Generics make code type-safe for any type. Lambdas are short functions; the Stream API filters, maps, sorts and groups data.
import java.util.*;
import java.util.function.Function;
import java.util.stream.*;
public class Main {
record Student(String name, String cls, int marks) {}
static <T> T applyTwice(Function<T, T> f, T x) { return f.apply(f.apply(x)); }
static <T extends Comparable<T>> T max(List<T> list) { return Collections.max(list); }
public static void main(String[] args) {
List<Student> s = List.of(new Student("Asha", "8A", 88), new Student("Ravi", "8B", 95), new Student("Meena", "8A", 72));
System.out.println(s.stream().filter(x -> x.marks() >= 80).map(Student::name).collect(Collectors.toList()));
System.out.println(s.stream().sorted(Comparator.comparingInt(Student::marks).reversed()).map(Student::name).toList());
System.out.println(new TreeMap<>(s.stream().collect(Collectors.groupingBy(Student::cls, Collectors.averagingInt(Student::marks)))));
System.out.println(applyTwice(n -> n * 3, 2) + " " + applyTwice(t -> t + "!", "hi") + " " + max(List.of(4, 9, 2)));
}
}
✅ Verified output
[Asha, Ravi]
[Ravi, Asha, Meena]
{8A=80.0, 8B=95.0}
18 hi!! 9
Concurrency: threads and executors
An ExecutorService runs tasks on a pool of threads; Future gets each result. AtomicInteger is a thread-safe counter, so 4 threads × 1000 increments give exactly 4000.
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(3);
long start = System.currentTimeMillis();
List<Future<Integer>> results = new ArrayList<>();
for (int i = 1; i <= 3; i++) {
final int n = i;
results.add(pool.submit(() -> { Thread.sleep(200); return n * n; }));
}
List<Integer> squares = new ArrayList<>();
for (Future<Integer> f : results) squares.add(f.get());
System.out.println("squares " + squares + " fast: " + (System.currentTimeMillis() - start < 550));
AtomicInteger counter = new AtomicInteger();
List<Callable<Void>> jobs = new ArrayList<>();
for (int t = 0; t < 4; t++) jobs.add(() -> { for (int i = 0; i < 1000; i++) counter.incrementAndGet(); return null; });
pool.invokeAll(jobs);
pool.shutdown();
System.out.println("counter " + counter.get());
}
}
✅ Verified output
squares [1, 4, 9] fast: true counter 4000
References, equality and the heap
Objects live on the heap and variables hold references. Two references can point to one object. equals/hashCode decide value equality, which HashSet relies on.
import java.util.*;
public class Main {
static class Point {
final int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override public boolean equals(Object o) { return o instanceof Point p && p.x == x && p.y == y; }
@Override public int hashCode() { return Objects.hash(x, y); }
}
static void addMark(List<Integer> list) { list.add(100); } // changes the caller's list
public static void main(String[] args) {
List<Integer> a = new ArrayList<>(List.of(1, 2));
List<Integer> b = a; // same object
List<Integer> c = new ArrayList<>(a); // copy
addMark(a);
System.out.println("a " + a + " b " + b + " c " + c + " same: " + (a == b));
Point p1 = new Point(1, 2), p2 = new Point(1, 2);
System.out.println("== " + (p1 == p2) + " equals " + p1.equals(p2) + " set size " + new HashSet<>(List.of(p1, p2)).size());
List<Integer> fixed = List.of(1, 2);
try { fixed.add(3); } catch (UnsupportedOperationException e) { System.out.println("List.of is immutable"); }
}
}
✅ Verified output
a [1, 2, 100] b [1, 2, 100] c [1, 2] same: true == false equals true set size 1 List.of is immutable
🛠️ Set up and verify
- Install
winget install EclipseAdoptium.Temurin.21.JDK
brew install openjdk@21
sudo apt install openjdk-21-jdk
- Editor: Visual Studio Code with Extension Pack for Java (or IntelliJ IDEA Community).
- Create
Hello.javapublic class Hello { public static void main(String[] args) { System.out.println("Hello, GayaGnan!"); } } - Run it and check the version
javac Hello.java && java Hello java -version
- Health check: paste what your terminal printed.
Install one language at a time. These commands only install or print versions; nothing here deletes files. Versions change: check the official page and note the version you installed.
🧭 The 14-lab path
- 00 · Setup and verify — install the runtime and editor, run hello world, print the version, fix PATH
- 01 · Syntax and trace — follow each line and the value of every variable
- 02 · Logic — conditionals and decision trees
- 03 · Loops — trace tables and loop challenges
- 04 · Functions — break problems down and test each piece
- 05 · Data structures — lists, maps, stacks, queues and trees you can see
- 06 · Debugging — bug cases with a hint ladder
- 07 · Algorithms — search, sort and Big-O comparison
- 08 · Files and data — read and write fixture files
- 09 · API or database — talk to a mock API or a sandbox database
- 10 · Testing — write tests and read failing assertions
- 11 · Security — secure coding and dependency hygiene for this language
- 12 · Project — one portfolio mini-project
- 13 · Assessment — quiz, coding test, rubric and an explain-your-answer prompt
📚 Modules
- syntax
- control flow
- methods
- arrays and strings
- classes and objects
- encapsulation
- inheritance and interfaces
- exceptions
- collections
- generics
- streams
- files
- concurrency basics
- JUnit testing
- JDBC concepts
- Spring Boot REST
🏗️ Mini-projects
- Library management (in-memory)
- Bank account simulator (no real money)
- REST student service
- Order workflow
Projects use synthetic data only.