No snippets match your search.
In-depth Java tutorials with practical, copy-paste ready code examples. Learn core concepts, best practices, and real-world techniques.
Couldn't load quote
Java is one of the most widely used programming languages in the world, powering everything from enterprise backend systems to Android mobile applications. Originally developed by James Gosling at Sun Microsystems in 1995, Java follows the "write once, run anywhere" philosophy — your compiled Java code can run on any device that has a Java Virtual Machine (JVM) installed, regardless of the underlying operating system or hardware architecture.
Before writing your first Java program, it is important to understand the three core components of the Java ecosystem. The JVM (Java Virtual Machine) is the runtime engine that executes compiled Java bytecode. The JRE (Java Runtime Environment) includes the JVM plus the core class libraries needed to run Java applications. The JDK (Java Development Kit) includes everything in the JRE plus the development tools like the Java compiler (javac), debugger, and jar packaging tool. To develop Java applications, you need the JDK installed on your machine.
Unlike interpreted languages such as Python or JavaScript, Java uses a two-step compilation process. First, the Java compiler (javac) translates your .java source files into bytecode stored in .class files. This bytecode is not native machine code — it is an intermediate representation. Second, the JVM reads this bytecode and uses its Just-In-Time (JIT) compiler to translate frequently executed bytecode paths into optimized native machine code at runtime. This hybrid approach gives Java both platform independence and competitive performance.
Every Java application must have at least one class, and execution begins in the main method. Here is the classic "Hello World" program with a detailed breakdown of each component:
Let's break this down line by line. public is an access modifier making the class accessible from everywhere. class declares a new class, and HelloWorld is the class name — which must match the filename (HelloWorld.java). The static keyword means the method belongs to the class itself, not to an instance, so the JVM can call it without creating an object. void means the method returns no value. String[] args is an array of command-line arguments.
Pro Tip
In Java 11 and later, you can run a single-file Java program directly with java HelloWorld.java without manually compiling it first. The JVM compiles and runs it in one step — great for quick testing and learning.
To start coding in Java, you have several options. The most straightforward approach is to download the latest LTS (Long Term Support) JDK from Adoptium (Eclipse Temurin), Oracle, or Amazon Corretto. As of 2024, Java 21 is the latest LTS release and is recommended for most projects. For an IDE, IntelliJ IDEA Community Edition (free) is the most popular choice among professional Java developers. Alternatives include Eclipse, VS Code with the Java Extension Pack, and Apache NetBeans.
If you prefer not to install anything locally, you can use online Java compilers like Replit, OnlineGDB, or JDoodle to practice writing and running Java code directly in your browser. These tools are excellent for following along with the tutorials on this page.
main method as the entry point.Before diving into advanced topics like frameworks and concurrency, you need a solid understanding of Java's fundamental building blocks. This tutorial covers the core concepts that form the foundation of every Java program: data types, variables, operators, control flow statements, and methods. Mastering these basics will make learning everything else significantly easier.
Java is a statically typed language, meaning every variable must be declared with a type before it can be used, and that type cannot change. Java has eight primitive data types divided into four categories: integer types (byte, short, int, long), floating-point types (float, double), character type (char), and boolean type (boolean). Unlike many other languages, Java's primitives have fixed sizes across all platforms — an int is always 32 bits whether you run on a 32-bit or 64-bit system.
Common Mistake
Never use float or double for financial calculations. They suffer from floating-point precision issues. For example, 0.1 + 0.2 does not equal 0.3 exactly. Use java.math.BigDecimal for money instead.
Control flow statements determine the order in which your code executes. Java provides the standard if/else and switch statements for branching, and for, while, and do-while for looping. Starting with Java 14, the enhanced switch expression was introduced, which is more concise, prevents fall-through bugs, and can be used as an expression that returns a value.
A method (also called a function in other languages) is a reusable block of code that performs a specific task. In Java, every method must be declared inside a class. Methods promote code reusability, make your programs easier to read and maintain, and allow you to break complex problems into smaller, manageable pieces. A well-designed method should do one thing, do it well, and have a clear name that describes what it does.
Pro Tip
Keep methods short — ideally under 20 lines. If a method is getting long, look for opportunities to extract sub-tasks into separate methods. This follows the Single Responsibility Principle and makes your code much easier to test and debug.
var for local variables when the type is obvious from context.BigDecimal.Java is an object-oriented programming (OOP) language at its core. Unlike procedural programming where you write sequences of instructions, OOP organizes code around objects — self-contained entities that combine data (fields) and behavior (methods). Understanding OOP is essential for writing clean, maintainable Java code, and it is a fundamental skill tested in virtually every Java job interview.
There are four pillars of OOP that you need to understand: Encapsulation, Inheritance, Polymorphism, and Abstraction. Let's explore each one with practical Java examples.
A class is a blueprint or template that defines the structure and behavior of its objects. An object is a specific instance of a class. Think of a class as the architectural drawing for a house, and objects as the actual houses built from that drawing. Each house (object) has the same structure but can have different values for its properties — different paint colors, different addresses, and so on.
Encapsulation is the practice of hiding an object's internal state and requiring all interaction to be performed through an object's methods. In Java, you achieve this by declaring fields as private and providing public getter and setter methods. This gives you control over how the data is accessed and modified — you can add validation, logging, or trigger side effects when a field changes. In the BankAccount example above, the deposit method validates that the amount is positive before modifying the balance, preventing invalid state.
Inheritance allows a new class (subclass) to inherit fields and methods from an existing class (superclass), promoting code reuse. Polymorphism (meaning "many forms") allows objects of different subclasses to be treated as objects of their common superclass, with the correct method implementation being called at runtime. This is one of the most powerful features of OOP — it lets you write flexible code that works with a superclass type while automatically using the correct subclass behavior.
Pro Tip
Favor composition over inheritance. Instead of creating deep inheritance hierarchies, prefer composing objects from smaller, focused components. This makes your code more flexible and easier to change. Use inheritance only when there is a clear "is-a" relationship, and prefer implementing interfaces for polymorphism.
If you need a simple class that primarily holds data (a "data carrier"), Java 16 introduced records as a concise way to declare immutable classes. A record automatically generates the constructor, accessor methods, equals(), hashCode(), and toString() for you. What would take 30+ lines of boilerplate with a traditional class can be expressed in a single line.
Exceptions are Java's mechanism for handling runtime errors gracefully. Instead of crashing your program when something goes wrong — a file not found, a network timeout, or invalid user input — Java's exception handling system lets you detect, report, and recover from errors in a structured way. Understanding how exceptions work is critical for writing robust, production-quality Java applications.
Java divides exceptions into two categories. Checked exceptions (subclasses of Exception but not RuntimeException) must be either caught with a try-catch block or declared in the method signature with throws. These represent conditions that a reasonable application might want to recover from, such as IOException or SQLException. Unchecked exceptions (subclasses of RuntimeException) do not need to be declared or caught. These typically represent programming errors like NullPointerException, IllegalArgumentException, or IndexOutOfBoundsException.
One of the most common sources of resource leaks in Java is forgetting to close files, database connections, or network sockets. The try-with-resources statement (introduced in Java 7) solves this problem elegantly. Any object that implements the AutoCloseable interface can be declared in the try statement header, and Java guarantees it will be closed automatically when the block exits — whether it exits normally or due to an exception. This eliminates the need for a finally block just to close resources.
Common Mistake
Never use an empty catch block like catch (Exception e) { }. This silently swallows errors and makes debugging extremely difficult. At minimum, log the exception. Only catch exceptions you can actually handle — let the rest propagate up the call stack.
When the built-in exception types don't accurately describe the error condition in your domain, you should create custom exceptions. This makes your error handling more expressive and allows callers to catch and handle specific business errors differently. A custom exception is simply a class that extends Exception (for checked) or RuntimeException (for unchecked). Always provide constructors that accept a message and a cause (the original exception that triggered this one) for proper exception chaining.
The Java Collections Framework (JCF) is a unified architecture for representing and manipulating collections of objects. It provides interfaces (like List, Set, Map), implementations (like ArrayList, HashSet, HashMap), and algorithms (like sorting and searching) that you use in virtually every Java program. Understanding which collection to use in a given situation is a hallmark of an experienced Java developer.
The three most important collection interfaces serve different purposes. A List is an ordered collection that allows duplicates and provides positional access (you can get elements by index). Use a List when you need to maintain insertion order or access elements by position. A Set is a collection that does not allow duplicate elements. Use a Set when you need to eliminate duplicates or check membership efficiently. A Map maps keys to values — each key can appear at most once. Use a Map when you need to associate values with keys and look them up quickly.
| Interface | Ordered? | Duplicates? | Best Implementation |
|---|---|---|---|
| List | Yes (by index) | Yes | ArrayList |
| Set | Depends on impl | No | HashSet |
| Map | Depends on impl | No (keys) | HashMap |
Not all sets behave the same way. HashSet offers the fastest operations (O(1) average) but does not maintain any order. LinkedHashSet maintains insertion order while still providing near-O(1) operations — use it when you need to iterate over elements in the order they were added. TreeSet keeps elements sorted in natural order (or by a custom Comparator) but operations take O(log n) time. The same pattern applies to maps: HashMap for speed, LinkedHashMap for insertion order, TreeMap for sorted keys.
Pro Tip
Program to interfaces, not implementations. Declare your variables as List<String> list = new ArrayList<>() rather than ArrayList<String> list = new ArrayList<>(). This makes it easy to swap implementations later without changing the rest of your code.
List for ordered data with duplicates, Set for uniqueness, Map for key-value lookups.List.of()) when data won't change.computeIfAbsent and merge for atomic map operations.The Stream API, introduced in Java 8, fundamentally changed how Java developers process collections of data. A stream is a sequence of elements supporting sequential and parallel aggregate operations. Unlike collections, which are data structures that store elements, streams are a pipeline for computing values from a data source. They don't store data themselves — they transport it through a series of operations to produce a result.
Streams encourage a declarative style of programming: instead of specifying exactly how to iterate and process each element (imperative), you describe what you want to achieve, and the stream framework figures out the how. This leads to more readable, more maintainable, and often more parallelizable code.
Every stream pipeline consists of three parts: a source (a collection, array, I/O channel, or generator function), zero or more intermediate operations (which transform the stream into another stream — like filter, map, sorted), and one terminal operation (which produces a result or a side effect — like collect, forEach, count). Intermediate operations are lazy — they don't execute until a terminal operation is invoked.
Streams are not always the best choice. Use streams when you are performing transformations, filtering, or aggregations on a collection — operations that naturally describe "what" you want. Streams shine with complex pipelines where the declarative style improves readability. However, use traditional for loops when you need to modify elements in place, when you need explicit control over iteration (like breaking early with complex conditions), or when dealing with primitive arrays where the boxing overhead of streams would hurt performance. In practice, most real-world codebases use a mix of both approaches.
Common Mistake
A stream can only be consumed once. If you try to use the same stream reference for two terminal operations, you'll get an IllegalStateException: stream has already been operated upon or closed. If you need to process the same data multiple times, either create a new stream from the source or collect the results into a collection first.
Pro Tip
Be careful with .parallelStream(). Parallel streams use the common ForkJoinPool, which has a fixed number of threads equal to available processors. For CPU-bound tasks on large datasets, parallelism can provide significant speedups. But for small collections, I/O-bound operations, or when running in a web server (where the common pool is shared across requests), parallel streams can actually be slower and cause thread starvation. Always benchmark before switching to parallel.
Copy-paste ready code examples for everyday Java development tasks. Use the filters below or search to find what you need.
No snippets match your search.