Java is a general-purpose, object-oriented programming language whose code is compiled into bytecode that runs on any machine with a Java Virtual Machine (JVM). This Java tutorial teaches it with Java 25, the current long-term support release.
Java is one of the most popular programming languages for developing all kinds of desktop applications, web applications and mobile apps. Today we mostly use it for back-end services and REST APIs built with Spring Boot, and for Android apps.
The following example is a complete Java 25 program. We save it as Hello.java and run it with the command java Hello.java.
void main() {
IO.println("Hello, Java!"); // Hello, Java!
}
Notice that the file has no class, and main() has no static keyword and no parameters. Java 25 allows this short form for small programs, and the classic public static void main(String[] args) form still works everywhere.
In the next sections, we install JDK 25 and run our first program in both forms. After that, a learning path in 12 steps links to the best tutorials for each topic, from language basics to unit tests, and a topic index lists every other Java tutorial on the site.
1. What Is Java and Which Version Should We Learn?
We write Java code in .java files. The javac compiler turns each file into a .class file that contains bytecode. Bytecode is a set of instructions for the JVM, not for a specific CPU, so the same .class file runs on every operating system that has a JVM.
We get all the tools in one download, the Java Development Kit (JDK). To get started, we need only three of its commands. The difference between JDK, JRE and JVM matters later.
| Tool | What it does | Example |
|---|---|---|
| javac | Compiles .java source files into .class files | javac HelloWorld.java |
| java | Starts the JVM and runs a class, or runs a source file without a separate compile step | java Hello.java |
| jshell | Runs single Java statements in an interactive console | jshell |
A new Java version comes out every six months, in March and in September. Every two years, one of them is a long-term support (LTS) release, which gets security fixes for many years. Java 25 (September 2025) is the latest LTS release, and the next planned LTS release is Java 29 in September 2027.
Beginners should learn on Java 25, because most new projects and frameworks such as Spring Boot 4 support it. Java 26 and Java 27 add new features, but each of them gets updates for six months only.
2. Installing JDK 25
Before we write any code, we need a JDK on our machine. Several vendors build the JDK from the same OpenJDK code, so our code runs the same on all of them. Eclipse Temurin is free and a good default for learning.
We install it in four steps.
- Download the JDK 25 installer for our operating system from the Temurin releases page.
- Run the installer. On Windows, select the options that set JAVA_HOME and add Java to the PATH (the list of folders where the terminal looks for commands).
- Open a new terminal window, so that it reads the new PATH.
- Check the versions of the java and javac commands.
$ java -version
openjdk version "25.0.4.1" 2026-08-18 LTS
OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)
OpenJDK 64-Bit Server VM Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS, mixed mode, sharing)
$ javac -version
javac 25.0.4.1
If the terminal prints an older version or “command not found”, the PATH still points to another JDK or to none. The guide for installing OpenJDK on Linux, macOS and Windows shows how to set JAVA_HOME and PATH on each system, and a separate guide covers installing Java on 64-bit Windows step by step.
We do not need an IDE to start, because a text editor and the java command are enough. An IDE helps once a program has several classes, or when we need a debugger.
3. Writing and Running Our First Java Program
Java 25 gives us two ways to write a program. The short form is for learning and small scripts, whereas the classic form is what we see in every Maven or Spring Boot project, so we learn both.
| Feature | Compact source file (Java 25) | Classic class |
|---|---|---|
| Class declaration | Not needed; the compiler creates the class for us | public class HelloWorld { … } |
| main() method | void main(), an instance method without parameters | public static void main(String[] args) |
| Imports | The common packages, such as java.util, are imported for us | Only java.lang; everything else needs an import |
| Run | java Hello.java | javac HelloWorld.java, followed by java HelloWorld |
The example code uses JDK 25.0.4.1, Maven 3.9.16 and JUnit 6.1.3, and it is in the java-tutorial folder on GitHub.
3.1. A Compact Source File With an Instance Main Method
A compact source file is a .java file with methods and fields but no class around them. The compiler puts them into a class that it creates for us. The main() method needs neither the static keyword nor the String[] args parameter. Both features became final in Java 25 with JEP 512.
Java 25 also adds the java.lang.IO class for console programs. The method IO.println() prints a line, and IO.readln() prints a prompt and reads the line that the user types. When there is no input left, IO.readln() returns null, so we check for it.
The following example asks for a name and prints a greeting. It uses List without an import statement, because a compact source file imports the common Java packages for us.
void main() {
String input = IO.readln("Your name: ");
String name = (input == null || input.isBlank()) ? "stranger" : input.strip();
List<String> topics = List.of("basics", "OOP", "collections");
IO.println("Hello, " + name + "! Start with " + topics.getFirst() + ".");
}
The java command compiles the file in memory and runs it, so no .class file is written. In the first run, we pipe “Lokesh” into the program, so the name does not show after the prompt. In the second run, the input is empty, and the program prints “stranger” instead of failing with a NullPointerException.
$ echo Lokesh | java Greeting.java
Your name: Hello, Lokesh! Start with basics.
$ java Greeting.java < /dev/null
Your name: Hello, stranger! Start with basics.
3.2. The Classic Class With public static void main()
In real projects, every program starts from a class with a public static void main(String[] args) method. The JVM calls this main() method without creating an object first, which is why it is static. The args array holds the command-line arguments.
The following example is the classic Hello World program. The file name must match the public class name, so we save it as HelloWorld.java.
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, Java!");
}
}
For the classic form, we compile and run in two steps. The javac command writes HelloWorld.class, and the java command takes the class name without the .class extension.
$ javac HelloWorld.java
$ java HelloWorld
Hello, Java!
3.3. Moving the Code Into a Maven Project With a JUnit Test
Once a program has more than one class, we use a build tool. Maven downloads the libraries we need, and it compiles and tests our code with one command. We can create a Java project with Maven from the command line or from the IDE, and it keeps the code in src/main/java and the tests in src/test/java.
The following example moves the greeting logic into a Greeter class, so that a test can check it.
public class Greeter {
public String greet(String name) {
if (name == null || name.isBlank()) {
return "Hello, stranger!";
}
return "Hello, " + name.strip() + "!";
}
}
A JUnit test is a method annotated with @Test that calls our code and compares the result with the expected value. If the values differ, assertEquals() fails the test, and Maven reports the failure.
@Test
void greetsByName() {
Greeter greeter = new Greeter();
assertEquals("Hello, Lokesh!", greeter.greet("Lokesh"));
}
We run the tests with mvn test. The GreeterTest class on GitHub also checks a name with extra spaces and three kinds of missing names, so Maven reports 5 tests for it.
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0 -- in com.howtodoinjava.tutorial.GreeterTest
[INFO] BUILD SUCCESS
4. Java Tutorial Learning Path in 12 Steps
Beginners often get stuck because they do not know what to learn next. We learn the topics in the order shown in the diagram, because each step uses the one before it. For example, streams make sense only after we know collections and lambdas.

4.1. Language Basics and Flow Control
Start with syntax and the basic building blocks of the Java language. Variables hold values of a data type such as int or boolean, and flow control statements such as if-else and loops decide which code runs. The Java keywords list shows which words, such as class or record, we cannot use as names.
- Java Variables
- Java Data Types with Examples
- Java Primitive Types
- Java Operators
- Java Flow Control Statements
- Java Naming Conventions
4.2. Classes, Objects and OOP
A class describes the fields and the methods of a kind of object, such as a Book with a title and a price. Object-oriented programming (OOP) adds four ideas on top of classes, namely encapsulation, inheritance, polymorphism and abstraction.
- Java OOP Concepts with Examples
- Java Class and Object
- Java Constructors
- Java Inheritance
- Difference between Interface and Abstract Class
- Java hashCode() and equals() Methods
4.3. Working With Strings
Java stores text in String objects. A String is immutable, so methods such as toUpperCase() return a new String and never change the original one. Knowing this early explains why we use StringBuilder to join strings in a loop.
- Java String Tutorial
- Why are Strings Immutable in Java?
- Java String Constant Pool
- Java String Concatenation Performance
- Java String split()
- Joining Strings With StringJoiner
4.4. Arrays and Collections
Arrays are the most basic container of multiple elements. Apart from the array, Java supports more advanced collection types. A List keeps elements in order, and a Map stores key-value pairs such as “apple” -> 5.
- Java Array
- Printing an Array
- Java Collection Tutorials
- Java List
- Java ArrayList Guide
- Complete Guide to Java HashMap
- Java Comparator
- Sorting Arrays, Lists and Maps in Java
4.5. Handling Exceptions
An exception is an object that Java throws when something goes wrong, such as a missing file. We catch it with try-catch to recover, or declare it with throws to let the caller decide.
- Java try catch finally
- Java Checked vs Unchecked Exceptions
- Java throw and throws Keywords
- Java try-with-resources
- Java Custom Exceptions
- How to Fix and Avoid NullPointerException
- Java Exception Handling Best Practices
4.6. Generics
Generics let a class or a method work with a type that we choose, as in List<String>. The compiler checks that we put only strings into a List<String>, so we see the mistake at compile time instead of a ClassCastException at runtime.
4.7. Lambdas and Streams
A lambda expression is a short way to pass a piece of code to a method, such as name -> name.length(). The Stream API uses lambdas to filter and transform the elements of a collection in a few lines, and the Java Streams guide collects every stream tutorial in one place.
- Lambda Expressions in Java
- Functional Interfaces in Java
- Java Method References
- Java Stream API Examples for Beginners
4.8. Threads and Concurrency
A thread runs code in parallel with other threads, so a server can handle many requests at the same time. Since Java 21, virtual threads are cheap enough to start one thread for each request.
- Guide to Java Concurrency
- How to Create and Start a New Thread
- Guide to ExecutorService in Java
- Java Virtual Threads
4.9. Files and I/O
Learn the most basic read-and-write operations on files in Java. The Files class from java.nio.file reads a small file in one call, whereas we read a large file line by line, so it never has to fit in memory. Serialization writes a whole object to a stream of bytes, which we can save to a file or send over the network.
- Java IO Tutorials
- Create a New File
- Java Read File to String
- Java Read a File Line by Line
- Java Write to File
- Append to a File
- Java Serialization Tutorials
4.10. Date and Time
The java.time package has a separate class for each kind of value, such as LocalDate for a birthday and ZonedDateTime when the time zone matters. New code uses java.time instead of the old java.util.Date class.
- Java Date and Time Tutorial
- Get Current Date and Time in Java
- Java DateTimeFormatter
- Number of Days between Two Dates
- Calculate Age From Date of Birth
4.11. Modern Java Features From Java 17 to Java 25
Recent Java versions make everyday code shorter. For example, a record replaces a data class full of getters, and a text block holds a multi-line string. The Java 25 features article covers the newest changes, including the compact source files from section 3.1, and the new Java features page lists the changes of every release since Java 8.
- Java 21 Features (LTS)
- Java Record Type
- Sealed Classes and Interfaces
- Java Switch Expressions
- Record Patterns and Pattern Matching
- Java Text Blocks
- Java var Keyword
- Java Versions and Features
4.12. Unit Testing With JUnit
A unit test checks one class or method on its own, like the GreeterTest in section 3.3. JUnit 6 is the current version, and it requires Java 17 or newer.
- JUnit 6 Tutorial with Examples
- JUnit 6 Maven Dependency
- JUnit @ParameterizedTest
- Unit Testing Best Practices
5. All Java Tutorials by Topic
The learning path covers the main topics in order. Many smaller tutorials do not fit into one step, so we group them by the kind of task they solve, and we can open any group once we know the basics from section 4.1.
5.1. Java Syntax Details
Small syntax rules come up in the first weeks of every Java job. For example, Java passes object references by value, so a method can change the fields of an object but cannot replace the caller’s variable.
- Java Is Pass-by-Value, Not Pass-by-Reference
- Java Comments and Javadoc
- Block Statements, Scope and Initializers
- Static Import in Java
- Wrapper Classes, Autoboxing and Unboxing
- Underscores in Numeric Literals
- Diamond Operator and Type Inference
- Double Brace Initialization and Its Alternatives
- printf() and String.format() Format Specifiers
5.2. Flow Control Statements
Each statement in this group has its own tutorial with the edge cases that cause bugs, such as fall-through in a switch or an off-by-one error in a for loop.
- if-else, else-if and Nested if
- switch Statement, Fall-Through and Patterns
- Ternary Operator
- for Loop
- Enhanced for-each Loop
- while Loop
- do-while Loop
- break Statement
- continue Statement
- Labeled break and continue in Nested Loops
5.3. Java Objects, Annotations and Reflection
Once we write our own classes, we need to decide which ones are immutable, how they print themselves and how we copy or compare them. Frameworks such as Spring and Hibernate read annotations through reflection, so a basic look at both helps when we debug them.
- Immutable Class in Java
- Override toString() With ToStringBuilder or Records
- Builder Pattern for Java Records
- Guide to Object Cloning
- Compare Objects Without Getters
- Convert an Object to a Map
- Object Identity String With Objects.toIdentityString()
- Tuples in Java
- Pair in Java
- Call an Interface Default Method
- Cleaner API as a Replacement for finalize()
- Java Annotations Tutorial
- Java Reflection Examples
- Create an Object Without the new Keyword
5.4. Newer Language Features and Java 8 APIs
Each tutorial in this group covers one feature rather than a whole release. The Java 8 APIs such as Optional and Predicate appear in almost every Spring Boot code base, while the newer ones replace boilerplate such as casts after instanceof.
- Pattern Matching for instanceof
- Unnamed Variables and Patterns
- Sequenced Collections
- String Templates (Withdrawn in JDK 23)
- Java Optional Guide
- orElse() vs orElseGet()
- Java Predicate
- Java forEach()
5.5. Math and Numbers
Number handling has a few traps, such as integer overflow that wraps around without an error and double values that cannot store 0.1 without a rounding error. The tutorials in this group show the safe method for each case, from rounding to random numbers.
- Round a Double to 2 Decimal Places
- Math.ceil() vs floor() vs round()
- Math.abs() vs Math.absExact()
- Math.divideExact()
- Detect Integer Overflow With Exact Math Methods
- Binary, Octal, Hex and Decimal Conversion
- Convert Hex to ASCII and ASCII to Hex
- Generate Random Numbers
- Random Number in a Range
- Generate a UUID
5.6. XML Processing
The JDK includes parsers for XML, so we can read a configuration file or a SOAP response without extra libraries. DOM loads the whole document into memory, whereas SAX and StAX read it as a stream, which matters for files of several hundred megabytes.
- DOM vs SAX vs StAX Parser
- Convert a String to an XML Document
- Convert an XML Document to a String
- Read XML With the SAX Parser
- Read XML With the StAX Parser
- Read XML With JDOM2
- Convert XML to Properties and Back
- Java XPath Tutorial
- XPath Get Attribute Value
- XPath Find Nodes by Attribute Value
- XPath Check if a Node Exists
- XPath Namespace Resolution
5.7. JSON, PDF, Excel and CSV
Business apps often export reports or import data that a user uploads. For JSON, most projects use Jackson or Gson, and for large Excel files, a streaming reader keeps the memory use low.
- Read and Write JSON With JSON.simple
- Convert a JSON Array to a List
- Read and Write CSV Files With OpenCSV
- Read and Write PDF With iText
- Generate a PDF From an HTML Template
- Read Large Excel Files With the Apache POI SAX Parser
- Read and Write Large Excel Files With FastExcel
5.8. Testing Libraries
JUnit runs the tests, and the libraries in this group help us test the parts that JUnit alone does not cover. For example, WireMock and MockWebServer replace a remote REST API, so a test does not depend on a server we do not control.
- REST-assured Tutorial
- REST-assured POST and PUT Requests
- REST-assured XML Requests
- Mock REST APIs With WireMock
- MockWebServer With JUnit
- EasyMock Tutorial
- PowerMock With JUnit and Mockito
- Compare JSON With JSONassert
- Jayway JsonPath Tutorial
- Compare XML With XMLUnit
- XmlAssert Assertions
- UI Testing With Selenide
- Benchmark Code With JMH
5.9. HTTP, Email and Other Libraries
A few small libraries solve common jobs in back-end code, such as calling another REST API or sending a password-reset email. Each tutorial also names the modern alternative when the library is no longer the first choice.
- Apache HttpClient 5 Examples
- Parse HTML With Jsoup
- Send Email Using Gmail SMTP
- Dozer Bean Mapping and MapStruct
- JPAstreamer Guide
- Java Chatbot With AIML
- Docker Hello World for Java
5.10. Security and Cryptography
A weak security setting still compiles and runs, so mistakes in security code stay hidden until an attack. For example, a plain SHA-256 hash is fast to compute, so an attacker with a leaked password table can test many guesses per second, whereas bcrypt or Argon2 makes each guess slow on purpose.
- Password Hashing With PBKDF2, bcrypt and Argon2
- AES-256 Encryption and Decryption
- File Checksum With MD5 and SHA-256
- Base64 Encoding and Decoding
- Base64 Encode and Decode a File
- Secure Random Numbers With SecureRandom
- JSON Web Token (JWT) With jjwt
- REST API Security Guide
- SSL Certificate Errors and Safer Fixes
5.11. Puzzles and Interview Programs
Coding interviews for Java roles often start with a short program, such as reversing a string or finding a missing number. More solved programs are in the Java examples collection, and the Java algorithms page covers sorting and searching.
- FizzBuzz Program
- Reverse a String
- Palindrome Program
- Factorial Program
- Find the Missing Number in an Array
- Detect a Loop in a Linked List
- Remove Consecutive Duplicate Characters
- Check if a String Is a Pangram
5.12. Tools, JVM and Logging
Sooner or later we run Java outside the IDE, on a build server or in production. The tutorials in this group explain the command-line tools, JVM settings and logging setup we meet there.
- Set the Java Classpath
- Java System Properties
- Set Windows Environment Variables Without Admin Rights
- Create and Extract a JAR File
- View Bytecode With javap
- java.exe vs javaw.exe
- Little Endian vs Big Endian
- Soft Heap Limit With -XX:SoftMaxHeapSize
- Fluent Logging With Flogger
- Delete Old Logs on Rollover in Log4j2
6. Conclusion
We learn Java on JDK 25, the current LTS release. A compact source file with void main() is enough for a first program, and java Hello.java runs it in one step.
Real projects still use a class with public static void main(String[] args), built with Maven and tested with JUnit. After that, the learning path takes us from language basics to unit testing, one step at a time, and the topic index helps when a task needs something more specific, such as XPath or AES encryption.
7. References
- JEP 512 Compact Source Files and Instance Main Methods
- JDK 25 project page
- Java SE 25 documentation
- The java command
- Oracle Java SE Support Roadmap
- Learn Java at dev.java
- Maven in 5 Minutes
- JUnit User Guide
Happy Learning !!