40 Advanced Java Tutorials
1. Multithreading Basics
Multithreading allows concurrent execution of multiple threads to improve performance in Java applications.
Example: Creating a thread by extending Thread class.
public class MyThread extends Thread {
public void run() {
System.out.println("Thread running: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
Thread running: Thread-0
Note: Threads can be created by extending Thread or implementing Runnable. Use start() to begin execution, not run().
2. Thread Synchronization
Synchronization prevents thread interference and ensures thread-safe access to shared resources.
Example: Synchronized method for thread safety.
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
System.out.println("Count: " + count);
}
public static void main(String[] args) {
Counter counter = new Counter();
new Thread(() -> counter.increment()).start();
new Thread(() -> counter.increment()).start();
}
}
Count: 1
Count: 2
Note: Use synchronized keyword for methods or blocks. It prevents race conditions but may reduce performance.
3. Executor Framework
Executor framework simplifies thread management by providing a thread pool for task execution.
Example: Using ExecutorService.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorDemo {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> System.out.println("Task executed."));
executor.shutdown();
}
}
Task executed.
Note: ExecutorService manages thread lifecycle. Use shutdown() to gracefully terminate. Suitable for scalable applications.
4. Fork/Join Framework
Fork/Join framework divides tasks into smaller subtasks for parallel execution, ideal for recursive problems.
Example: Recursive sum using ForkJoinPool.
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class ForkJoinSum extends RecursiveTask {
private final int[] array;
private final int start, end;
public ForkJoinSum(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start <= 2) {
int sum = 0;
for (int i = start; i < end; i++) sum += array[i];
return sum;
}
int mid = start + (end - start) / 2;
ForkJoinSum left = new ForkJoinSum(array, start, mid);
ForkJoinSum right = new ForkJoinSum(array, mid, end);
left.fork();
return right.compute() + left.join();
}
public static void main(String[] args) {
int[] array = {1, 2, 3, 4};
ForkJoinPool pool = ForkJoinPool.commonPool();
int sum = pool.invoke(new ForkJoinSum(array, 0, array.length));
System.out.println("Sum: " + sum);
}
}
Sum: 10
Note: Fork/Join is efficient for divide-and-conquer tasks like parallel array processing. Use RecursiveTask for computations.
5. Java NIO
Java NIO (New I/O) provides non-blocking I/O operations for scalable file and network handling.
Example: Reading a file using NIO.
import java.nio.file.Files;
import java.nio.file.Paths;
public class NIODemo {
public static void main(String[] args) throws Exception {
String content = Files.readString(Paths.get("input.txt"));
System.out.println("File content: " + content);
}
}
File content: Hello, NIO!
Note: NIO uses channels and buffers for efficient I/O. It's faster than traditional IO for large files or network operations.
6. Lambda Expressions
Lambda expressions enable functional programming by treating code as data.
Example: Sorting a list with lambda.
import java.util.Arrays;
import java.util.List;
public class LambdaDemo {
public static void main(String[] args) {
List numbers = Arrays.asList(3, 1, 4);
numbers.sort((a, b) -> a - b);
System.out.println("Sorted: " + numbers);
}
}
Sorted: [1, 3, 4]
Note: Lambdas work with functional interfaces. They simplify code for tasks like sorting or event handling.
7. Stream API
Stream API processes collections functionally, enabling operations like filter and map.
Example: Filtering even numbers.
import java.util.Arrays;
import java.util.List;
public class StreamDemo {
public static void main(String[] args) {
List numbers = Arrays.asList(1, 2, 3, 4);
List evens = numbers.stream()
.filter(n -> n % 2 == 0)
.toList();
System.out.println("Evens: " + evens);
}
}
Evens: [2, 4]
Note: Streams are lazy and support parallel processing. Avoid side effects in stream operations for predictable results.
8. Optional Class
Optional handles null values safely, reducing NullPointerExceptions.
Example: Using Optional to handle null.
import java.util.Optional;
public class OptionalDemo {
public static void main(String[] args) {
Optional name = Optional.ofNullable(null);
String result = name.orElse("Unknown");
System.out.println("Name: " + result);
}
}
Name: Unknown
Note: Use Optional.of() for non-null values and ofNullable() for potentially null values. Simplifies null checks.
9. Functional Interfaces
Functional interfaces have one abstract method and are used with lambdas.
Example: Custom functional interface.
@FunctionalInterface
interface MyFunction {
int apply(int x);
}
public class FunctionalInterfaceDemo {
public static void main(String[] args) {
MyFunction square = x -> x * x;
System.out.println("Square: " + square.apply(5));
}
}
Square: 25
Note: Use @FunctionalInterface to enforce one method. Common examples include Predicate and Function.
10. Method References
Method references provide a concise way to refer to methods in lambda expressions.
Example: Using method reference for printing.
import java.util.Arrays;
public class MethodReferenceDemo {
public static void main(String[] args) {
Arrays.asList("a", "b").forEach(System.out::println);
}
}
a
b
Note: Use :: for method references. They simplify lambda expressions for static methods, instance methods, or constructors.
11. Concurrent Collections
Concurrent collections like ConcurrentHashMap handle thread-safe operations without explicit synchronization.
Example: Using ConcurrentHashMap.
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentMapDemo {
public static void main(String[] args) {
ConcurrentHashMap map = new ConcurrentHashMap<>();
map.put("key", 1);
System.out.println("Value: " + map.get("key"));
}
}
Value: 1
Note: ConcurrentHashMap avoids locking the entire map, improving performance in multithreaded applications.
12. Java Reflection API
Reflection allows inspection and modification of classes, methods, and fields at runtime.
Example: Accessing a class's methods.
import java.lang.reflect.Method;
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
Class> cls = String.class;
Method[] methods = cls.getMethods();
System.out.println("Method count: " + methods.length);
}
}
Method count: [Number of String methods]
Note: Use reflection for dynamic code but avoid it in performance-critical applications due to overhead.
13. Annotations
Annotations provide metadata for code, used for configuration or runtime processing.
Example: Creating a custom annotation.
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String value();
}
@MyAnnotation(value = "Test")
public class AnnotationDemo {
public static void main(String[] args) {
MyAnnotation anno = AnnotationDemo.class.getAnnotation(MyAnnotation.class);
System.out.println("Annotation value: " + anno.value());
}
}
Annotation value: Test
Note: Annotations like @Override are built-in. Custom annotations require retention policies for runtime access.
14. Generics in Depth
Generics provide type safety and flexibility for collections and classes.
Example: Generic class usage.
public class GenericBox {
private T item;
public void setItem(T item) { this.item = item; }
public T getItem() { return item; }
public static void main(String[] args) {
GenericBox box = new GenericBox<>();
box.setItem("Test");
System.out.println("Item: " + box.getItem());
}
}
Item: Test
Note: Generics eliminate casting and ensure type safety. Use bounded types for restricting allowed types.
15. Custom Exceptions
Custom exceptions allow defining application-specific error conditions.
Example: Creating a custom exception.
class CustomException extends Exception {
public CustomException(String message) {
super(message);
}
}
public class CustomExceptionDemo {
public static void main(String[] args) {
try {
throw new CustomException("Error occurred!");
} catch (CustomException e) {
System.out.println("Caught: " + e.getMessage());
}
}
}
Caught: Error occurred!
Note: Extend Exception for checked or RuntimeException for unchecked exceptions. Use for specific error handling.
16. Java Memory Management
Java memory management handles object allocation in heap and stack, managed by the JVM.
Example: Demonstrating heap allocation.
public class MemoryDemo {
public static void main(String[] args) {
Object obj = new Object();
System.out.println("Object created in heap.");
}
}
Object created in heap.
Note: Heap stores objects, stack stores method calls. Garbage collector reclaims unused memory automatically.
17. Garbage Collection Tuning
Garbage collection tuning optimizes JVM performance by configuring GC algorithms.
Example: Setting GC options (conceptual).
public class GCTuning {
public static void main(String[] args) {
System.out.println("Run with: -XX:+UseG1GC");
}
}
Run with: -XX:+UseG1GC
Note: Use G1GC for low-latency applications. Tune heap size with -Xmx and -Xms for performance.
18. Java Modules (JPMS)
Java Platform Module System (JPMS) provides modularity for better encapsulation.
Example: Defining a module (module-info.java).
module com.example {
exports com.example;
}
public class ModuleDemo {
public static void main(String[] args) {
System.out.println("Module defined.");
}
}
Module defined.
Note: Use module-info.java to define modules. Enhances security and maintainability in large projects.
19. JDBC Advanced
JDBC (Java Database Connectivity) enables advanced database operations like batch processing.
Example: Batch update with JDBC.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
public class JDBCDemo {
public static void main(String[] args) throws Exception {
Connection conn = DriverManager.getConnection("jdbc:h2:mem:test");
PreparedStatement ps = conn.prepareStatement("INSERT INTO users VALUES (?, ?)");
ps.setInt(1, 1); ps.setString(2, "Alice"); ps.addBatch();
ps.setInt(1, 2); ps.setString(2, "Bob"); ps.addBatch();
ps.executeBatch();
System.out.println("Batch executed.");
}
}
Batch executed.
Note: Batch updates improve performance for multiple queries. Always close connections to avoid leaks.
20. Hibernate Basics
Hibernate is an ORM framework that simplifies database interactions using object-relational mapping.
Example: Persisting an entity with Hibernate.
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateDemo {
public static void main(String[] args) {
SessionFactory factory = new Configuration().configure().buildSessionFactory();
Session session = factory.openSession();
session.beginTransaction();
session.persist(new User(1, "Alice"));
session.getTransaction().commit();
System.out.println("User persisted.");
}
}
class User {
private int id;
private String name;
public User(int id, String name) { this.id = id; this.name = name; }
// Getters and setters
}
User persisted.
Note: Hibernate reduces boilerplate SQL. Requires configuration files like hibernate.cfg.xml.
21. Spring Core
Spring Core provides dependency injection and inversion of control for modular applications.
Example: Basic Spring bean configuration.
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class AppConfig {
@Bean
public MyService myService() { return new MyService(); }
}
class MyService {
public String getMessage() { return "Hello, Spring!"; }
}
public class SpringDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService service = context.getBean(MyService.class);
System.out.println(service.getMessage());
}
}
Hello, Spring!
Note: Spring manages beans via IoC. Use annotations like @Bean for configuration.
22. Spring Boot Basics
Spring Boot simplifies Spring application setup with auto-configuration and embedded servers.
Example: Simple Spring Boot application.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootDemo {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemo.class, args);
System.out.println("Spring Boot started.");
}
}
Spring Boot started.
Note: @SpringBootApplication enables auto-configuration. Ideal for rapid development of production-ready apps.
23. RESTful Web Services
RESTful services in Java use Spring to create APIs with HTTP methods like GET and POST.
Example: Simple REST controller.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RestDemo {
@GetMapping("/hello")
public String hello() {
return "Hello, REST!";
}
}
Hello, REST!
Note: Use @RestController and @GetMapping for REST endpoints. Spring Boot simplifies REST API development.
24. WebSocket Implementation
WebSockets enable real-time, bidirectional communication in Java applications.
Example: Simple WebSocket server.
import jakarta.websocket.OnMessage;
import jakarta.websocket.server.ServerEndpoint;
@ServerEndpoint("/echo")
public class WebSocketDemo {
@OnMessage
public String onMessage(String message) {
return "Received: " + message;
}
}
Received: Hello
Note: Use @ServerEndpoint for WebSocket endpoints. Ideal for chat apps or live updates.
25. Java Security API
Java Security API provides tools for encryption, authentication, and secure communication.
Example: Generating a hash.
import java.security.MessageDigest;
public class SecurityDemo {
public static void main(String[] args) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest("test".getBytes());
System.out.println("Hash generated.");
}
}
Hash generated.
Note: Use SHA-256 or AES for secure data. Java's security APIs ensure data integrity and confidentiality.
26. Concurrent Programming Patterns
Concurrent patterns like producer-consumer manage thread interactions efficiently.
Example: Producer-consumer with BlockingQueue.
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class ProducerConsumer {
public static void main(String[] args) {
BlockingQueue queue = new LinkedBlockingQueue<>();
new Thread(() -> queue.offer(1)).start();
new Thread(() -> System.out.println("Consumed: " + queue.poll())).start();
}
}
Consumed: 1
Note: BlockingQueue simplifies producer-consumer. Patterns ensure thread safety and coordination.
27. CompletableFuture
CompletableFuture enables asynchronous programming with non-blocking task chaining.
Example: Chaining asynchronous tasks.
import java.util.concurrent.CompletableFuture;
public class CompletableFutureDemo {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + ", World!")
.thenAccept(System.out::println);
}
}
Hello, World!
Note: Use supplyAsync for tasks. Chain with thenApply or thenAccept for sequential execution.
28. Reactive Programming with Reactor
Reactor enables reactive programming with non-blocking, event-driven streams.
Example: Creating a Flux stream.
import reactor.core.publisher.Flux;
public class ReactorDemo {
public static void main(String[] args) {
Flux.just(1, 2, 3)
.map(n -> n * 2)
.subscribe(System.out::println);
}
}
2
4
6
Note: Use Flux for multiple items, Mono for single. Ideal for high-throughput, asynchronous systems.
29. Java Serialization
Serialization converts objects to byte streams for storage or transmission.
Example: Serializing an object.
import java.io.Serializable;
import java.io.ObjectOutputStream;
import java.io.FileOutputStream;
public class SerializationDemo implements Serializable {
private String data = "Test";
public static void main(String[] args) throws Exception {
SerializationDemo obj = new SerializationDemo();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data.ser"));
out.writeObject(obj);
out.close();
System.out.println("Object serialized.");
}
}
Object serialized.
Note: Implement Serializable for serialization. Use for caching or network transmission, but consider security risks.
30. Dynamic Proxies
Dynamic proxies create runtime implementations of interfaces for AOP or logging.
Example: Creating a proxy.
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;
interface MyInterface {
void sayHello();
}
public class ProxyDemo {
public static void main(String[] args) {
InvocationHandler handler = (proxy, method, args) -> {
System.out.println("Proxy invoked.");
return null;
};
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class[]{MyInterface.class},
handler);
proxy.sayHello();
}
}
Proxy invoked.
Note: Dynamic proxies are used in frameworks like Spring AOP. They intercept method calls dynamically.
31. Java Performance Tuning
Performance tuning optimizes Java applications for speed and resource usage.
Example: Using StringBuilder for string concatenation.
public class PerformanceDemo {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i);
}
System.out.println("String built efficiently.");
}
}
String built efficiently.
Note: Use StringBuilder over String concatenation in loops. Profile with tools like VisualVM for bottlenecks.
32. JMX Monitoring
JMX (Java Management Extensions) monitors and manages Java applications at runtime.
Example: Registering an MBean.
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
public interface MyMBean {
int getCounter();
}
public class MyBean implements MyMBean {
private int counter = 0;
public int getCounter() { return counter; }
}
public class JMXDemo {
public static void main(String[] args) throws Exception {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("com.example:type=MyBean");
server.registerMBean(new MyBean(), name);
System.out.println("MBean registered.");
}
}
MBean registered.
Note: JMX enables runtime monitoring of metrics like memory usage. Use with tools like JConsole.
33. Java Logging Frameworks
Logging frameworks like Log4j provide structured logging for debugging and monitoring.
Example: Using SLF4J with Log4j.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggingDemo {
private static final Logger logger = LoggerFactory.getLogger(LoggingDemo.class);
public static void main(String[] args) {
logger.info("Application started.");
System.out.println("Log written.");
}
}
Log written.
Note: SLF4J is a facade for Log4j or Logback. Configure log levels and appenders for detailed logs.
34. Design Patterns in Java
Design patterns provide reusable solutions to common software problems.
Example: Singleton pattern.
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println("Singleton instance created.");
}
}
Singleton instance created.
Note: Common patterns include Singleton, Factory, and Observer. Use for maintainable, scalable code.
35. Microservices with Spring Boot
Microservices break applications into small, independent services using Spring Boot.
Example: Simple microservice.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
public class MicroserviceDemo {
public static void main(String[] args) {
SpringApplication.run(MicroserviceDemo.class, args);
}
}
@RestController
class MicroController {
@GetMapping("/service")
public String service() {
return "Microservice running.";
}
}
Microservice running.
Note: Use Spring Cloud for service discovery and load balancing. Microservices enhance scalability and deployment flexibility.
36. JavaFX Basics
JavaFX creates modern GUI applications with rich graphics.
Example: Simple JavaFX window.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class JavaFXDemo extends Application {
@Override
public void start(Stage stage) {
Label label = new Label("Hello, JavaFX!");
StackPane root = new StackPane(label);
Scene scene = new Scene(root, 200, 100);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
[Displays window with "Hello, JavaFX!"]
Note: JavaFX replaces Swing for GUI apps. Use FXML for declarative UI design.
37. GraalVM and Native Images
GraalVM compiles Java to native code for faster startup and lower memory usage.
Example: Simple GraalVM native image (conceptual).
public class GraalDemo {
public static void main(String[] args) {
System.out.println("Run with: native-image -cp . GraalDemo");
}
}
Run with: native-image -cp . GraalDemo
Note: Native images reduce startup time. Use GraalVM for cloud-native or serverless applications.
38. Java Testing with JUnit
JUnit is a framework for writing and running unit tests in Java.
Example: Writing a JUnit test.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class JUnitDemo {
@Test
void testAddition() {
assertEquals(4, 2 + 2);
System.out.println("Test passed.");
}
}
Test passed.
Note: Use @Test for test methods. Assertions like assertEquals verify expected results.
39. Mockito for Mocking
Mockito creates mock objects for testing by simulating dependencies.
Example: Mocking a list.
import org.mockito.Mockito;
import java.util.List;
public class MockitoDemo {
public static void main(String[] args) {
List mockedList = Mockito.mock(List.class);
Mockito.when(mockedList.get(0)).thenReturn("Test");
System.out.println("Mocked value: " + mockedList.get(0));
}
}
Mocked value: Test
Note: Mockito simplifies testing by isolating dependencies. Use when() and thenReturn() for behavior setup.
40. Java Concurrency Utilities
Concurrency utilities like CountDownLatch and CyclicBarrier manage complex thread interactions.
Example: Using CountDownLatch.
import java.util.concurrent.CountDownLatch;
public class LatchDemo {
public static void main(String[] args) throws Exception {
CountDownLatch latch = new CountDownLatch(2);
new Thread(() -> { latch.countDown(); }).start();
new Thread(() -> { latch.countDown(); }).start();
latch.await();
System.out.println("All threads completed.");
}
}
All threads completed.
Note: CountDownLatch synchronizes threads waiting for others to complete. Useful for task coordination.