Java Complete Tutorial: Beginner to Advanced (90 Topics with Code) — a practical guide to Java complete tutorial with clear examples you can reuse in real projects.
This complete Java tutorial covers 90 topics — from installation and syntax to OOP, collections, streams, JDBC, HTTP APIs, Maven/Gradle, testing, and a final Java application project.
Course roadmap
- Introduction to Java
- What is Java?
- Features of Java
- Java JDK, JRE and JVM
- Installing Java
- Setting Up IntelliJ IDEA / Eclipse
- First Java Program
- Java Program Structure
- Java Syntax
- Variables and Constants in Java
- Primitive Data Types in Java
- Type Casting in Java
- Operators in Java
- Input and Output in Java
- Conditional Statements in Java
- if, else if and else in Java
- switch Statement in Java
- Loops in Java
- for Loop in Java
- while Loop in Java
- do-while Loop in Java
- break and continue in Java
- Arrays in Java
- Multidimensional Arrays in Java
- Strings in Java
- String Methods in Java
- StringBuilder and StringBuffer
- Methods in Java
- Method Parameters in Java
- Method Overloading in Java
- Recursion in Java
- Classes and Objects in Java
- Constructors in Java
- this Keyword in Java
- Static Members in Java
- Access Modifiers in Java
- Encapsulation in Java
- Inheritance in Java
- Types of Inheritance in Java
- Method Overriding in Java
- Polymorphism in Java
- Abstraction in Java
- Abstract Classes in Java
- Interfaces in Java
- Packages in Java
- Exception Handling in Java
- try, catch, finally in Java
- throw and throws in Java
- Custom Exceptions in Java
- Collections Framework in Java
- ArrayList in Java
- LinkedList in Java
- HashSet in Java
- TreeSet in Java
- HashMap in Java
- TreeMap in Java
- Iterator in Java
- Generics in Java
- Wrapper Classes in Java
- Enum in Java
- Lambda Expressions in Java
- Functional Interfaces in Java
- Stream API in Java
- Optional Class in Java
- Date and Time API in Java
- File Handling in Java
- Reading and Writing Files in Java
- Serialization in Java
- Multithreading in Java
- Threads and Runnable in Java
- Synchronization in Java
- Concurrency in Java
- JDBC Introduction
- Connecting Java with MySQL
- CRUD Operations with JDBC
- Java Networking
- HTTP Requests in Java
- REST API Integration in Java
- JSON Handling in Java
- Maven in Java
- Gradle in Java
- Unit Testing with JUnit
- Debugging Java Applications
- Logging in Java
- Java Design Patterns
- SOLID Principles in Java
- Java Security Best Practices
- Java Interview Questions
- Java Practical Projects
- Final Project – Complete Java Application
1. Introduction to Java
Java is a popular, object-oriented language used for backend services, Android (historically), enterprise apps, and APIs. This series covers syntax, OOP, collections, streams, JDBC, and project tooling.
- Install JDK and set up an IDE.
- Practice syntax, OOP, and collections.
- Build JDBC/API features and the final project.
Learning path
Basics → control flow → arrays/strings
Methods/OOP → collections/generics
Streams/files/threads → JDBC/APIs
Maven/JUnit → final Java app
2. What is Java?
You write `.java` source, compile to bytecode (`.class`), and run it on any system with a compatible JVM — “write once, run anywhere.”
Mental model
Source (.java) → javac → Bytecode (.class) → JVM → Output
3. Features of Java
Java’s strengths include the JVM ecosystem, mature tooling, multithreading support, and large enterprise adoption.
Feature highlights
- Object-oriented
- Platform independent (JVM)
- Automatic memory management (GC)
- Multithreading support
- Large standard library & ecosystem
4. Java JDK, JRE and JVM
JVM runs bytecode. JRE includes JVM + libraries to run apps. JDK includes JRE + compilers/tools to develop apps.
Quick map
JDK = develop (javac, tools) + run
JRE = run apps
JVM = executes bytecode
5. Installing Java
Install Temurin/Oracle/OpenJDK, set JAVA_HOME if needed, then verify versions.
- Download a current LTS JDK.
- Add Java to PATH / set JAVA_HOME.
- Verify with `java -version` and `javac -version`.
Verify install
java -version
javac -version
6. Setting Up IntelliJ IDEA / Eclipse
Create a project, point it to your JDK, and run a main class from the IDE.
IDE checklist
1. Install IntelliJ IDEA or Eclipse
2. Configure Project SDK (JDK)
3. Create Java project/module
4. Add Main class and Run
7. First Java Program
Every beginner starts with a `main` method. Compile with `javac` and run with `java`.
HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, Java!");
}
}
// javac HelloWorld.java
// java HelloWorld
8. Java Program Structure
A typical Java file has a package declaration, imports, and one public class matching the file name.
Structure sketch
package com.example.demo;
import java.util.List;
public class App {
public static void main(String[] args) {
// start here
}
}
9. Java Syntax
Java is case-sensitive and uses braces `{}` for blocks. Statements usually end with semicolons.
Syntax basics
// single-line comment
/* multi-line */
int count = 10;
if (count > 0) {
System.out.println(count);
}
10. Variables and Constants in Java
Variables store values. Use `final` for values that should not be reassigned.
Variables + final
int age = 25;
double price = 99.5;
final double PI = 3.14159;
11. Primitive Data Types in Java
Primitives are not objects. Choose types based on range and precision needs.
Primitives
int i = 10;
long big = 1_000_000_000L;
double d = 3.14;
char c = 'A';
boolean ok = true;
12. Type Casting in Java
Widening is safe/automatic (int→long). Narrowing needs an explicit cast and may lose data.
Casting
int a = 10;
double b = a; // widening
int c = (int) 9.8; // narrowing → 9
13. Operators in Java
Operators build expressions for math and decisions. Know precedence or use parentheses.
Operators
int x = 10;
System.out.println(x + 3);
System.out.println(x > 5 && x < 20);
x += 2;
14. Input and Output in Java
`Scanner` is a simple way to read console input while learning. Close resources in larger apps.
Scanner I/O
import java.util.Scanner;
Scanner sc = new Scanner(System.in);
System.out.print("Name: ");
String name = sc.nextLine();
System.out.println("Hello, " + name);
sc.close();
15. Conditional Statements in Java
Conditionals run different code paths based on true/false expressions.
Basic if
int score = 75;
if (score >= 50) {
System.out.println("Pass");
} else {
System.out.println("Fail");
}
16. if, else if and else in Java
Only one branch runs. Order conditions from most specific to general when needed.
Grade example
int marks = 82;
if (marks >= 90) {
System.out.println("A");
} else if (marks >= 75) {
System.out.println("B");
} else if (marks >= 50) {
System.out.println("C");
} else {
System.out.println("F");
}
17. switch Statement in Java
Modern Java supports switch expressions. Always cover defaults for unexpected values.
Switch expression
int day = 3;
String name = switch (day) {
case 1 -> "Mon";
case 2 -> "Tue";
case 3 -> "Wed";
default -> "Other";
};
System.out.println(name);
18. Loops in Java
Loops prevent copy-paste. Pick the loop style that matches your condition/count needs.
Loop idea
for (int i = 1; i <= 3; i++) {
System.out.println(i);
}
19. for Loop in Java
`for` is ideal when you know the iteration count or iterate indexed data.
for + for-each
for (int i = 0; i < 3; i++) {
System.out.println(i);
}
int[] nums = {1, 2, 3};
for (int n : nums) {
System.out.println(n);
}
20. while Loop in Java
Ensure the condition eventually becomes false to avoid infinite loops.
while example
int n = 3;
while (n > 0) {
System.out.println(n);
n--;
}
21. do-while Loop in Java
Useful for menus and input validation where one execution is required first.
do-while
int i = 0;
do {
System.out.println(i);
i++;
} while (i < 3);
22. break and continue in Java
`break` stops the loop. `continue` jumps to the next iteration.
break/continue
for (int i = 1; i <= 5; i++) {
if (i == 2) continue;
if (i == 4) break;
System.out.println(i);
}
23. Arrays in Java
Arrays have a fixed length after creation. Indexes start at 0.
Array basics
int[] nums = {10, 20, 30};
System.out.println(nums.length);
System.out.println(nums[1]);
24. Multidimensional Arrays in Java
A common form is `int[][]` for rows and columns.
2D array
int[][] matrix = {
{1, 2},
{3, 4}
};
System.out.println(matrix[1][0]); // 3
25. Strings in Java
Strings are immutable in Java. Use methods that return new strings instead of changing the original.
String basics
String s = "Java";
System.out.println(s.length());
System.out.println(s.toUpperCase());
26. String Methods in Java
Compare strings with `equals`, not `==` (reference comparison).
Useful methods
String s = " Hello Java ";
System.out.println(s.trim());
System.out.println(s.contains("Java"));
System.out.println("a,b".split(",")[0]);
System.out.println("Java".equals("Java"));
27. StringBuilder and StringBuffer
Prefer `StringBuilder` for most single-threaded string building. `StringBuffer` is synchronized.
StringBuilder
StringBuilder sb = new StringBuilder();
sb.append("Hello").append(' ').append("Java");
System.out.println(sb.toString());
28. Methods in Java
Methods reduce duplication. Use `static` methods for utilities that do not need object state.
Method example
public class MathUtils {
public static int add(int a, int b) {
return a + b;
}
public static void main(String[] args) {
System.out.println(add(2, 3));
}
}
29. Method Parameters in Java
Java is pass-by-value. For objects, the reference value is copied — you can mutate the object, but reassigning the parameter does not change the caller’s reference.
Parameters
static void greet(String name) {
System.out.println("Hi " + name);
}
30. Method Overloading in Java
Overloading is resolved at compile time based on argument types/count.
Overloading
static int sum(int a, int b) { return a + b; }
static double sum(double a, double b) { return a + b; }
31. Recursion in Java
Every recursive method needs a base case. Prefer iteration when recursion depth is large.
Factorial
static long factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
32. Classes and Objects in Java
Java is class-based OOP. Almost all application code lives in classes.
Class + object
class User {
String name;
}
User u = new User();
u.name = "Asha";
33. Constructors in Java
Constructors share the class name and have no return type. You can overload constructors.
Constructor
class User {
String name;
User(String name) { this.name = name; }
}
User u = new User("Ravi");
34. this Keyword in Java
`this` disambiguates fields from parameters and can call other constructors (`this(…)`).
this usage
class Point {
int x;
Point(int x) { this.x = x; }
}
35. Static Members in Java
`static` members belong to the class. The `main` method is static so the JVM can call it without an object.
Static field
class Counter {
static int count = 0;
Counter() { count++; }
}
36. Access Modifiers in Java
Encapsulation relies on choosing the narrowest useful visibility.
Visibility
private → class only
(default) → package
protected → package + subclasses
public → everywhere
37. Encapsulation in Java
Keep fields private and validate changes through methods.
Encapsulated class
class Account {
private double balance;
public double getBalance() { return balance; }
public void deposit(double amount) {
if (amount > 0) balance += amount;
}
}
38. Inheritance in Java
Java supports single class inheritance. Use interfaces for multiple contracts.
extends
class Animal {
String speak() { return "..."; }
}
class Dog extends Animal {
@Override
String speak() { return "Woof"; }
}
39. Types of Inheritance in Java
Multiple class inheritance is not allowed; use interfaces instead.
Supported forms
Single class inheritance
Multilevel
Hierarchical
Multiple via interfaces
40. Method Overriding in Java
Overriding enables runtime polymorphism. Signatures must match rules for covariant returns/exceptions.
Override
class Printer {
void print() { System.out.println("base"); }
}
class PdfPrinter extends Printer {
@Override
void print() { System.out.println("pdf"); }
}
41. Polymorphism in Java
Polymorphism lets one interface/base type drive many implementations.
Runtime polymorphism
Animal a = new Dog();
System.out.println(a.speak()); // Woof
42. Abstraction in Java
Callers depend on what a type can do, not how it does it.
Abstraction idea
payment.charge(100) // caller
// hidden: network, auth, retries
43. Abstract Classes in Java
Abstract classes can mix concrete methods and abstract methods. They cannot be instantiated directly.
Abstract class
abstract class Shape {
abstract double area();
}
class Circle extends Shape {
double r;
Circle(double r) { this.r = r; }
double area() { return Math.PI * r * r; }
}
44. Interfaces in Java
Modern Java interfaces can include default/static methods. Classes may implement many interfaces.
Interface
interface Notifier {
void send(String to);
}
class EmailNotifier implements Notifier {
public void send(String to) { /* send email */ }
}
45. Packages in Java
Packages prevent name clashes and map to folder structure (`com.example.app`).
Package + import
package com.example.app;
import java.util.ArrayList;
public class Main {}
46. Exception Handling in Java
Checked vs unchecked exceptions matter in Java APIs. Handle or declare checked exceptions.
try/catch
try {
int n = Integer.parseInt("abc");
} catch (NumberFormatException e) {
System.out.println("Invalid number");
}
47. try, catch, finally in Java
Prefer try-with-resources for AutoCloseable resources like files/streams.
try-with-resources
try (var reader = new java.io.FileReader("data.txt")) {
// read
} catch (java.io.IOException e) {
e.printStackTrace();
}
48. throw and throws in Java
`throw` creates/raises an exception. `throws` advertises checked exceptions a method may pass to callers.
throw/throws
static int parse(String s) throws NumberFormatException {
if (s == null) throw new IllegalArgumentException("null");
return Integer.parseInt(s);
}
49. Custom Exceptions in Java
Extend `Exception` (checked) or `RuntimeException` (unchecked) based on API needs.
Custom exception
class InsufficientFunds extends RuntimeException {
InsufficientFunds(String msg) { super(msg); }
}
50. Collections Framework in Java
The Collections Framework provides reusable data structures and algorithms under `java.util`.
Core interfaces
List → ordered, allows duplicates
Set → unique elements
Map → key/value pairs
51. ArrayList in Java
`ArrayList` grows automatically and is one of the most used List implementations.
ArrayList
import java.util.ArrayList;
ArrayList<String> names = new ArrayList<>();
names.add("Asha");
names.add("Ravi");
System.out.println(names.get(0));
52. LinkedList in Java
`LinkedList` implements List and Deque. Prefer ArrayList unless you need linked-list behavior.
LinkedList as deque
import java.util.LinkedList;
LinkedList<String> q = new LinkedList<>();
q.addLast("a");
q.addLast("b");
System.out.println(q.removeFirst());
53. HashSet in Java
`HashSet` ignores duplicates and offers average O(1) add/contains.
HashSet
import java.util.HashSet;
HashSet<Integer> set = new HashSet<>();
set.add(1);
set.add(1);
System.out.println(set.size()); // 1
54. TreeSet in Java
`TreeSet` uses natural ordering or a Comparator and does not allow null (in typical use).
TreeSet
import java.util.TreeSet;
TreeSet<String> set = new TreeSet<>();
set.add("c");
set.add("a");
System.out.println(set); // [a, c]
55. HashMap in Java
`HashMap` allows one null key and is unordered. Prefer it for most key/value needs.
HashMap
import java.util.HashMap;
HashMap<String, Integer> ages = new HashMap<>();
ages.put("Asha", 28);
System.out.println(ages.get("Asha"));
56. TreeMap in Java
`TreeMap` keeps keys sorted and is useful for range-like ordered maps.
TreeMap
import java.util.TreeMap;
TreeMap<String, Integer> map = new TreeMap<>();
map.put("b", 2);
map.put("a", 1);
System.out.println(map.firstKey()); // a
57. Iterator in Java
Use Iterator when you need to remove elements during traversal.
Iterator remove
import java.util.*;
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().equals("b")) it.remove();
}
58. Generics in Java
Generics catch type errors at compile time and remove most casting.
Generic box
class Box<T> {
private T value;
void set(T value) { this.value = value; }
T get() { return value; }
}
Box<String> box = new Box<>();
box.set("Java");
59. Wrapper Classes in Java
Autoboxing converts between primitives and wrappers automatically.
Autoboxing
Integer x = 10; // autobox
int y = x; // unbox
List<Integer> nums = List.of(1, 2, 3);
60. Enum in Java
Enums are type-safe and can include fields/methods.
Enum example
enum Status { DRAFT, PUBLISHED }
Status s = Status.DRAFT;
System.out.println(s);
61. Lambda Expressions in Java
Lambdas work with functional interfaces and unlock Stream API style code.
Lambda
Runnable r = () -> System.out.println("run");
r.run();
java.util.List<String> names = java.util.List.of("b", "a");
names.stream().sorted().forEach(System.out::println);
62. Functional Interfaces in Java
Common ones: `Predicate`, `Function`, `Consumer`, `Supplier`, and custom `@FunctionalInterface` types.
Predicate example
import java.util.function.Predicate;
Predicate<Integer> even = n -> n % 2 == 0;
System.out.println(even.test(4));
63. Stream API in Java
Streams are not data structures — they process sequences of elements in a pipeline.
Stream pipeline
import java.util.List;
List<Integer> nums = List.of(1, 2, 3, 4, 5);
int sum = nums.stream()
.filter(n -> n % 2 == 0)
.mapToInt(n -> n)
.sum();
System.out.println(sum);
64. Optional Class in Java
`Optional` makes absence explicit. Avoid using it for every field — best as a return type.
Optional
import java.util.Optional;
Optional<String> name = Optional.ofNullable(null);
System.out.println(name.orElse("Guest"));
65. Date and Time API in Java
Prefer `java.time` over legacy `Date`/`Calendar` for new code.
java.time
import java.time.*;
LocalDate today = LocalDate.now();
LocalDate nextWeek = today.plusDays(7);
System.out.println(today + " → " + nextWeek);
66. File Handling in Java
Use try-with-resources and NIO `Files` helpers for cleaner file code.
Write file
import java.nio.file.*;
Files.writeString(Path.of("notes.txt"), "Learning Javan");
67. Reading and Writing Files in Java
Choose charset consciously (UTF-8) for portable text files.
Read lines
import java.nio.file.*;
import java.util.List;
List<String> lines = Files.readAllLines(Path.of("notes.txt"));
lines.forEach(System.out::println);
68. Serialization in Java
Java serialization is powerful but sensitive — for APIs prefer JSON. Use serialization carefully when required.
Serializable marker
import java.io.Serializable;
class User implements Serializable {
private static final long serialVersionUID = 1L;
String name;
}
69. Multithreading in Java
Multithreading improves throughput but adds complexity — synchronization and race conditions matter.
Start a thread
Thread t = new Thread(() -> System.out.println("worker"));
t.start();
70. Threads and Runnable in Java
Prefer `ExecutorService` over manually managing threads in real apps.
ExecutorService
import java.util.concurrent.*;
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(() -> System.out.println("task"));
pool.shutdown();
71. Synchronization in Java
Synchronize the smallest critical section necessary to avoid race conditions.
synchronized method
class Counter {
private int value;
public synchronized void inc() { value++; }
public synchronized int get() { return value; }
}
72. Concurrency in Java
Explore `ConcurrentHashMap`, futures, locks, and atomic variables for production concurrency.
ConcurrentHashMap
import java.util.concurrent.ConcurrentHashMap;
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("visits", 1);
map.merge("visits", 1, Integer::sum);
73. JDBC Introduction
JDBC provides connections, statements, and result sets. Always use prepared statements.
JDBC flow
Load driver (modern drivers auto-load)
Get Connection
Prepare statement
Execute + map ResultSet
Close resources
74. Connecting Java with MySQL
Add the MySQL driver dependency, then connect with `jdbc:mysql://host:3306/db`.
Connection example
import java.sql.DriverManager;
var conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/app", "root", "");
conn.close();
75. CRUD Operations with JDBC
Prepared statements prevent SQL injection and improve reuse.
Insert + select
var ps = conn.prepareStatement("INSERT INTO users(name) VALUES (?)");
ps.setString(1, "Asha");
ps.executeUpdate();
var rs = conn.prepareStatement("SELECT id, name FROM users").executeQuery();
while (rs.next()) {
System.out.println(rs.getInt("id") + " " + rs.getString("name"));
}
76. Java Networking
Start with `HttpClient` for HTTP apps; use sockets for custom protocols.
Networking options
java.net.http.HttpClient → REST/HTTP
Socket/ServerSocket → custom TCP
URI/URL → resource identifiers
77. HTTP Requests in Java
`HttpClient` is the modern standard-library way to call APIs.
GET request
import java.net.URI;
import java.net.http.*;
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.example.com/data")).GET().build();
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.statusCode());
System.out.println(res.body());
78. REST API Integration in Java
Combine HttpClient with a JSON library (Jackson/Gson) for practical integrations.
POST JSON sketch
HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.example.com/items"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{"title":"Hello"}"))
.build();
79. JSON Handling in Java
JSON is the common API format. Map JSON to POJOs for cleaner code.
Jackson sketch
// ObjectMapper mapper = new ObjectMapper();
// MyDto dto = mapper.readValue(json, MyDto.class);
// String out = mapper.writeValueAsString(dto);
80. Maven in Java
Maven uses `pom.xml` for dependencies, plugins, and the standard project layout.
Useful commands
mvn clean package
mvn test
mvn dependency:tree
81. Gradle in Java
Gradle is flexible and fast with incremental builds — popular for many modern Java projects.
Useful commands
gradle build
gradle test
gradle run
82. Unit Testing with JUnit
Tests document behavior and catch regressions. Keep them fast and focused.
JUnit 5 test
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class MathTest {
@Test
void addsNumbers() {
assertEquals(5, 2 + 3);
}
}
83. Debugging Java Applications
Reproduce the bug, inspect variables, fix the root cause, then add a test.
Debug tips
Read stack traces top-down
Use breakpoints/conditional breakpoints
Watch variables & evaluate expressions
Fix + regression test
84. Logging in Java
Prefer a logging facade (SLF4J) in real projects. Avoid `System.out` for production diagnostics.
java.util.logging
import java.util.logging.Logger;
Logger log = Logger.getLogger("app");
log.info("Server started");
85. Java Design Patterns
Patterns communicate design intent. Use them where they reduce complexity, not for decoration.
Builder sketch
class User {
final String name;
final String email;
private User(Builder b) { name = b.name; email = b.email; }
static class Builder {
String name, email;
Builder name(String n) { name = n; return this; }
Builder email(String e) { email = e; return this; }
User build() { return new User(this); }
}
}
86. SOLID Principles in Java
SOLID improves flexibility and testing. Pair with interfaces and dependency injection.
SOLID reminder
S single responsibility
O open/closed
L Liskov substitution
I interface segregation
D dependency inversion
87. Java Security Best Practices
Use prepared statements, validate input, keep dependencies updated, and never hardcode secrets.
Security checklist
- PreparedStatement for SQL
- Validate/sanitize input
- TLS for network calls
- Least-privilege DB users
- Dependency vulnerability scanning
88. Java Interview Questions
Be ready to explain JVM/JDK, HashMap internals at a high level, equals/hashCode, and checked vs unchecked exceptions.
Sample Q&A
Q: == vs equals?
A: == compares references for objects; equals compares values when overridden.
Q: ArrayList vs LinkedList?
A: ArrayList better random access; LinkedList better some insert/remove patterns.
Q: Checked vs unchecked?
A: Checked must be handled/declared; unchecked are RuntimeException.
89. Java Practical Projects
Project practice locks in syntax and OOP better than tutorials alone.
Project ideas
1. Console Todo app
2. Student CRUD with JDBC/MySQL
3. Weather CLI using HttpClient
4. Library inventory with collections
5. Multithreaded file downloader (advanced)
90. Final Project – Complete Java Application
Create a desktop/console or simple modular app (e.g., task manager or inventory) with services, repositories, MySQL, validation, and tests.
- Choose the domain and schema.
- Implement models/repositories/services.
- Wire a main menu or API entrypoint.
- Add tests and documentation.
Final project scope
1. Maven/Gradle project
2. Domain models + services
3. JDBC repository layer (MySQL)
4. Console or simple UI/API entrypoint
5. Exception handling + validation
6. JUnit tests for core logic
7. README + sample SQL schema
Suggested packages
com.example.app
model
repository
service
util
Main
Conclusion
You now have a full Java path: language fundamentals, OOP, collections/streams, files/concurrency, JDBC/APIs, and professional tooling. Finish the final Java application project to turn the lessons into portfolio work.