How to Use Multithreading in Java

Avatar

By squashlabs, Last Updated: Aug. 23, 2023

How to Use Multithreading in Java

Introduction to Multithreading

Multithreading is a powerful concept in Java that allows concurrent execution of multiple threads within a single program. A thread is a lightweight sub-process that can run concurrently with other threads, and each thread represents an independent flow of control. Multithreading is crucial for improving the performance and responsiveness of Java applications, especially in scenarios where tasks can be executed concurrently.

In Java, multithreading is achieved by extending the Thread class or implementing the Runnable interface. Let's take a look at an example of creating threads using both approaches:

// Extending the Thread classclass MyThread extends Thread {    public void run() {        // Code to be executed in the new thread    }}// Implementing the Runnable interfaceclass MyRunnable implements Runnable {    public void run() {        // Code to be executed in the new thread    }}public class Main {    public static void main(String[] args) {        // Creating and starting a new thread using the Thread class        MyThread thread1 = new MyThread();        thread1.start();        // Creating and starting a new thread using the Runnable interface        MyRunnable runnable = new MyRunnable();        Thread thread2 = new Thread(runnable);        thread2.start();    }}

In the above example, we create a thread by either extending the Thread class or implementing the Runnable interface. The run() method contains the code that will be executed in the new thread. To start the thread, we call the start() method, which internally calls the run() method.

Related Article: How To Convert String To Int In Java

Concepts of Multithreading

To effectively use multithreading in Java, it is important to understand some key concepts:

Thread States

Threads in Java can be in different states during their lifecycle. The main thread starts in the "RUNNABLE" state, indicating that it is ready for execution. Other possible states include "NEW" (when a thread is created but not yet started), "BLOCKED" (when a thread is waiting for a monitor lock), "WAITING" (when a thread is waiting indefinitely for another thread to perform a particular action), and "TERMINATED" (when a thread has completed its execution or terminated abnormally).

Thread Synchronization

When multiple threads access shared resources concurrently, synchronization is necessary to prevent race conditions and ensure data consistency. Java provides synchronized blocks and methods to achieve thread synchronization. Let's see an example of using synchronized blocks to synchronize access to a shared resource:

class Counter {    private int count;    public synchronized void increment() {        count++;    }    public int getCount() {        return count;    }}public class Main {    public static void main(String[] args) {        Counter counter = new Counter();        // Creating multiple threads that increment the counter        Thread thread1 = new Thread(() -> {            for (int i = 0; i < 1000; i++) {                counter.increment();            }        });        Thread thread2 = new Thread(() -> {            for (int i = 0; i < 1000; i++) {                counter.increment();            }        });        thread1.start();        thread2.start();        // Waiting for both threads to complete        try {            thread1.join();            thread2.join();        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Counter value: " + counter.getCount());    }}

In the above example, the increment() method of the Counter class is marked as synchronized. This ensures that only one thread can execute the method at a time, preventing concurrent access to the count variable and ensuring its integrity.

Related Article: How to Use the JsonProperty in Java

Multithreading Use Cases

Multithreading is useful in various scenarios where tasks can be executed concurrently. Some common use cases include:

Parallel Processing

Multithreading can be used to divide a large task into smaller sub-tasks that can be executed in parallel, thereby reducing the overall processing time. For example, in image processing, different threads can be assigned to process different regions of an image simultaneously.

Responsive User Interfaces

By using multithreading, time-consuming tasks such as network operations or database queries can be offloaded to separate threads, allowing the user interface to remain responsive. For instance, when downloading a file in a Java application, the download can be performed in a separate thread, while the main thread handles user interactions.

class DownloadTask implements Runnable {    public void run() {        // Code to download a file    }}public class Main {    public static void main(String[] args) {        // Creating a new thread to perform the download        Thread downloadThread = new Thread(new DownloadTask());        downloadThread.start();        // Continue with other operations in the main thread    }}

In the above example, the file download is performed in a separate thread, allowing the main thread to continue executing other tasks without being blocked.

Best Practices for Multithreading

When working with multithreading in Java, it is important to follow certain best practices to ensure efficient and correct execution. Here are some recommended practices:

Related Article: How to Use a Scanner Class in Java

1. Use Thread Pools

Creating a new thread for every task can be costly due to the overhead of thread creation and destruction. Instead, it is advisable to use thread pools, which manage a pool of reusable threads. This minimizes the overhead of thread creation and provides better control over the number of concurrent threads. Here's an example of using a thread pool:

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class Main {    public static void main(String[] args) {        // Creating a fixed-size thread pool with 5 threads        ExecutorService threadPool = Executors.newFixedThreadPool(5);        // Submitting tasks to the thread pool        for (int i = 0; i < 10; i++) {            threadPool.submit(() -> {                // Code to be executed in the thread            });        }        // Shutting down the thread pool        threadPool.shutdown();    }}

In the above example, a fixed-size thread pool with 5 threads is created using Executors.newFixedThreadPool(). Tasks are submitted to the thread pool using the submit() method, which takes a Runnable or Callable as a parameter. Finally, the shutdown() method is called to gracefully shut down the thread pool.

2. Use Thread Priorities Carefully

Thread priorities can be used to influence the scheduling behavior of threads. However, relying too much on thread priorities can lead to unpredictable results, as thread scheduling is ultimately controlled by the underlying operating system. It is generally recommended to avoid heavy reliance on thread priorities and design the application in a way that does not heavily depend on them.

3. Avoid Blocking Operations in the Main Thread

The main thread in a Java application is responsible for handling user interactions and keeping the user interface responsive. Blocking operations, such as long-running computations or I/O operations, should be performed in separate threads to prevent blocking the main thread and freezing the user interface.

Performance Considerations: CPU Usage

When designing multithreaded applications, it is important to consider CPU usage to ensure optimal performance. Here are some factors to keep in mind:

Related Article: Java Inheritance Tutorial

1. Avoid Busy Waiting

Busy waiting, also known as spinning, occurs when a thread continuously checks for a condition to become true without yielding the CPU. This consumes CPU cycles unnecessarily and can degrade performance. Instead, use synchronization mechanisms like locks, conditions, or semaphores to efficiently coordinate between threads.

2. Utilize Parallel Processing

Multithreading enables parallel processing, where multiple threads can execute different parts of a task simultaneously. By dividing a task into smaller sub-tasks and assigning them to separate threads, you can leverage the full power of modern CPUs and improve performance. However, keep in mind that not all tasks are suitable for parallel processing, and the overhead of managing threads may outweigh the benefits in some cases.

Performance Considerations: Memory Usage

Efficient memory usage is crucial for optimal performance in multithreaded applications. Here are some considerations to minimize memory usage:

1. Limit Object Creation

Creating too many objects can lead to excessive memory usage and increased garbage collection overhead. Reuse objects where possible instead of creating new ones in each thread. This can be achieved by using object pools or thread-local variables.

Related Article: Java Hibernate Interview Questions and Answers

2. Use Immutable Objects

Immutable objects are thread-safe by nature, as they cannot be modified after creation. They eliminate the need for synchronization when accessed concurrently, reducing the risk of data corruption and improving performance.

Performance Considerations: Thread Synchronization

Thread synchronization is a critical aspect of multithreading that can impact performance. Here are some considerations for efficient thread synchronization:

1. Minimize Lock Contention

Contention occurs when multiple threads try to acquire the same lock simultaneously, leading to contention delays and decreased performance. To minimize lock contention, use fine-grained locking, lock striping, or lock-free data structures where applicable.

2. Use Read/Write Locks

Read/write locks allow multiple threads to read a shared resource simultaneously, while exclusive write access is granted to a single thread. This can improve performance when the majority of operations are read operations, as multiple readers can proceed concurrently.

import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;class SharedResource {    private ReadWriteLock lock = new ReentrantReadWriteLock();    private int value;    public int getValue() {        lock.readLock().lock();        try {            return value;        } finally {            lock.readLock().unlock();        }    }    public void setValue(int newValue) {        lock.writeLock().lock();        try {            value = newValue;        } finally {            lock.writeLock().unlock();        }    }}public class Main {    public static void main(String[] args) {        SharedResource resource = new SharedResource();        // Multiple threads reading the shared value concurrently        for (int i = 0; i < 5; i++) {            new Thread(() -> {                int value = resource.getValue();                System.out.println("Value: " + value);            }).start();        }        // One thread writing the shared value        new Thread(() -> {            resource.setValue(42);        }).start();    }}

In the above example, a SharedResource class is created with a read/write lock. Multiple threads can read the value concurrently using the read lock, while the write lock ensures exclusive access when setting the value.

Related Article: Loading Single Elements from Java Data Structures

Advanced Techniques: Thread Pools

Thread pools provide a convenient way to manage and reuse threads in multithreaded applications. They offer better control over thread creation and destruction, leading to better performance and resource utilization. Here's an example of using the ExecutorService framework to create and manage a thread pool:

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class Main {    public static void main(String[] args) {        // Creating a fixed-size thread pool with 5 threads        ExecutorService threadPool = Executors.newFixedThreadPool(5);        // Submitting tasks to the thread pool        for (int i = 0; i < 10; i++) {            threadPool.submit(() -> {                // Code to be executed in the thread            });        }        // Shutting down the thread pool        threadPool.shutdown();    }}

In the above example, a fixed-size thread pool with 5 threads is created using Executors.newFixedThreadPool(). Tasks are submitted to the thread pool using the submit() method, which takes a Runnable or Callable as a parameter. Finally, the shutdown() method is called to gracefully shut down the thread pool.

Advanced Techniques: Thread Priorities

Thread priorities can be used to influence the scheduling behavior of threads in Java. However, it is important to use thread priorities judiciously, as they are not guaranteed to be honored by the underlying operating system. Here's an example of setting thread priorities:

class MyRunnable implements Runnable {    public void run() {        System.out.println("Thread priority: " + Thread.currentThread().getPriority());    }}public class Main {    public static void main(String[] args) {        // Creating threads with different priorities        Thread thread1 = new Thread(new MyRunnable());        thread1.setPriority(Thread.MIN_PRIORITY);        Thread thread2 = new Thread(new MyRunnable());        thread2.setPriority(Thread.NORM_PRIORITY);        Thread thread3 = new Thread(new MyRunnable());        thread3.setPriority(Thread.MAX_PRIORITY);        // Starting the threads        thread1.start();        thread2.start();        thread3.start();    }}

In the above example, three threads are created with different priorities: MIN_PRIORITY, NORM_PRIORITY, and MAX_PRIORITY. The run() method of the MyRunnable class prints the priority of the current thread using Thread.currentThread().getPriority().

Advanced Techniques: Thread Communication

Thread communication is essential when multiple threads need to synchronize their activities or exchange data. Java provides several mechanisms for thread communication, such as wait(), notify(), and notifyAll(). Here's an example of using these methods to implement thread communication:

class Producer implements Runnable {    private SharedQueue queue;    public Producer(SharedQueue queue) {        this.queue = queue;    }    public void run() {        try {            for (int i = 0; i < 10; i++) {                queue.put(i);                Thread.sleep(1000);            }        } catch (InterruptedException e) {            e.printStackTrace();        }    }}class Consumer implements Runnable {    private SharedQueue queue;    public Consumer(SharedQueue queue) {        this.queue = queue;    }    public void run() {        try {            while (true) {                int value = queue.get();                System.out.println("Consumed: " + value);                Thread.sleep(2000);            }        } catch (InterruptedException e) {            e.printStackTrace();        }    }}class SharedQueue {    private int value;    private boolean produced;    public synchronized void put(int newValue) throws InterruptedException {        while (produced) {            wait();        }        value = newValue;        produced = true;        notifyAll();    }    public synchronized int get() throws InterruptedException {        while (!produced) {            wait();        }        int retrievedValue = value;        produced = false;        notifyAll();        return retrievedValue;    }}public class Main {    public static void main(String[] args) {        SharedQueue sharedQueue = new SharedQueue();        Thread producerThread = new Thread(new Producer(sharedQueue));        Thread consumerThread = new Thread(new Consumer(sharedQueue));        producerThread.start();        consumerThread.start();    }}

In the above example, a SharedQueue class is used to exchange data between a producer and a consumer thread. The put() method of the SharedQueue class is responsible for adding a value to the queue, while the get() method retrieves a value from the queue. The producer thread continuously adds values to the queue, while the consumer thread consumes the values at a slower pace.

Code Snippet: Creating Threads

Creating threads in Java can be done by either extending the Thread class or implementing the Runnable interface. Here's an example of creating threads using both approaches:

// Extending the Thread classclass MyThread extends Thread {    public void run() {        // Code to be executed in the new thread    }}// Implementing the Runnable interfaceclass MyRunnable implements Runnable {    public void run() {        // Code to be executed in the new thread    }}public class Main {    public static void main(String[] args) {        // Creating and starting a new thread using the Thread class        MyThread thread1 = new MyThread();        thread1.start();        // Creating and starting a new thread using the Runnable interface        MyRunnable runnable = new MyRunnable();        Thread thread2 = new Thread(runnable);        thread2.start();    }}

In the above example, we create a thread by either extending the Thread class or implementing the Runnable interface. The run() method contains the code that will be executed in the new thread. To start the thread, we call the start() method, which internally calls the run() method.

Related Article: How To Set Xms And Xmx Parameters For Jvm

Code Snippet: Synchronizing Threads

Thread synchronization is crucial when multiple threads access shared resources concurrently. In Java, synchronization can be achieved using synchronized blocks or methods. Here's an example of using synchronized blocks to synchronize access to a shared resource:

class Counter {    private int count;    public synchronized void increment() {        count++;    }    public int getCount() {        return count;    }}public class Main {    public static void main(String[] args) {        Counter counter = new Counter();        // Creating multiple threads that increment the counter        Thread thread1 = new Thread(() -> {            for (int i = 0; i < 1000; i++) {                counter.increment();            }        });        Thread thread2 = new Thread(() -> {            for (int i = 0; i < 1000; i++) {                counter.increment();            }        });        thread1.start();        thread2.start();        // Waiting for both threads to complete        try {            thread1.join();            thread2.join();        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Counter value: " + counter.getCount());    }}

In the above example, the increment() method of the Counter class is marked as synchronized. This ensures that only one thread can execute the method at a time, preventing concurrent access to the count variable and ensuring its integrity.

Code Snippet: Using Thread Pools

Thread pools provide a convenient way to manage and reuse threads in multithreaded applications. Java's ExecutorService framework provides several implementations of thread pools. Here's an example of using a fixed-size thread pool:

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class Main {    public static void main(String[] args) {        // Creating a fixed-size thread pool with 5 threads        ExecutorService threadPool = Executors.newFixedThreadPool(5);        // Submitting tasks to the thread pool        for (int i = 0; i < 10; i++) {            threadPool.submit(() -> {                // Code to be executed in the thread            });        }        // Shutting down the thread pool        threadPool.shutdown();    }}

In the above example, a fixed-size thread pool with 5 threads is created using Executors.newFixedThreadPool(). Tasks are submitted to the thread pool using the submit() method, which takes a Runnable or Callable as a parameter. Finally, the shutdown() method is called to gracefully shut down the thread pool.

Code Snippet: Setting Thread Priorities

Thread priorities can be used to influence the scheduling behavior of threads in Java. However, it is important to use thread priorities judiciously, as they are not guaranteed to be honored by the underlying operating system. Here's an example of setting thread priorities:

class MyRunnable implements Runnable {    public void run() {        System.out.println("Thread priority: " + Thread.currentThread().getPriority());    }}public class Main {    public static void main(String[] args) {        // Creating threads with different priorities        Thread thread1 = new Thread(new MyRunnable());        thread1.setPriority(Thread.MIN_PRIORITY);        Thread thread2 = new Thread(new MyRunnable());        thread2.setPriority(Thread.NORM_PRIORITY);        Thread thread3 = new Thread(new MyRunnable());        thread3.setPriority(Thread.MAX_PRIORITY);        // Starting the threads        thread1.start();        thread2.start();        thread3.start();    }}

In the above example, three threads are created with different priorities: MIN_PRIORITY, NORM_PRIORITY, and MAX_PRIORITY. The run() method of the MyRunnable class prints the priority of the current thread using Thread.currentThread().getPriority().

Code Snippet: Implementing Thread Communication

Thread communication is essential when multiple threads need to synchronize their activities or exchange data. Java provides several mechanisms for thread communication, such as wait(), notify(), and notifyAll(). Here's an example of using these methods to implement thread communication:

class Producer implements Runnable {    private SharedQueue queue;    public Producer(SharedQueue queue) {        this.queue = queue;    }    public void run() {        try {            for (int i = 0; i < 10; i++) {                queue.put(i);                Thread.sleep(1000);            }        } catch (InterruptedException e) {            e.printStackTrace();        }    }}class Consumer implements Runnable {    private SharedQueue queue;    public Consumer(SharedQueue queue) {        this.queue = queue;    }    public void run() {        try {            while (true) {                int value = queue.get();                System.out.println("Consumed: " + value);                Thread.sleep(2000);            }        } catch (InterruptedException e) {            e.printStackTrace();        }    }}class SharedQueue {    private int value;    private boolean produced;    public synchronized void put(int newValue) throws InterruptedException {        while (produced) {            wait();        }        value = newValue;        produced = true;        notifyAll();    }    public synchronized int get() throws InterruptedException {        while (!produced) {            wait();        }        int retrievedValue = value;        produced = false;        notifyAll();        return retrievedValue;    }}public class Main {    public static void main(String[] args) {        SharedQueue sharedQueue = new SharedQueue();        Thread producerThread = new Thread(new Producer(sharedQueue));        Thread consumerThread = new Thread(new Consumer(sharedQueue));        producerThread.start();        consumerThread.start();    }}

In the above example, a SharedQueue class is used to exchange data between a producer and a consumer thread. The put() method of the SharedQueue class is responsible for adding a value to the queue, while the get() method retrieves a value from the queue. The producer thread continuously adds values to the queue, while the consumer thread consumes the values at a slower pace.

Related Article: Java Code Snippets for Everyday Problems

Error Handling in Multithreading

Error handling in multithreaded applications requires special attention to ensure proper handling of exceptions and prevent thread termination. Here are some best practices for error handling in multithreading:

1. Proper Exception Handling

Each thread should have its own exception handling mechanism to catch and handle exceptions. Unhandled exceptions in a thread can cause the thread to terminate, potentially affecting the overall application. It is recommended to catch exceptions within each thread and take appropriate action, such as logging the error or gracefully terminating the thread.

2. Thread Group Exception Handling

Java provides the ThreadGroup class, which can be used to handle uncaught exceptions thrown by threads in the same group. By setting an uncaught exception handler for the thread group, you can centrally manage exceptions that occur in multiple threads.

class MyThread extends Thread {    public MyThread(ThreadGroup group, Runnable target, String name) {        super(group, target, name);    }    public void run() {        try {            // Code that may throw an exception        } catch (Exception e) {            // Handle the exception        }    }}public class Main {    public static void main(String[] args) {        ThreadGroup threadGroup = new ThreadGroup("MyThreadGroup");        threadGroup.setUncaughtExceptionHandler((t, e) -> {            System.out.println("Uncaught exception in thread: " + t.getName());            e.printStackTrace();        });        MyThread thread1 = new MyThread(threadGroup, () -> {            // Code to be executed in the thread        }, "Thread1");        MyThread thread2 = new MyThread(threadGroup, () -> {            // Code to be executed in the thread        }, "Thread2");        thread1.start();        thread2.start();    }}

In the above example, a ThreadGroup named "MyThreadGroup" is created, and an uncaught exception handler is set using setUncaughtExceptionHandler(). The run() method of the MyThread class catches exceptions and handles them appropriately.

Real World Example: Database Access

Multithreading can greatly improve performance when accessing a database, especially in scenarios where multiple database queries can be executed concurrently. Here's an example of using multithreading for database access:

import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;class DatabaseAccess implements Runnable {    private Connection connection;    public DatabaseAccess(Connection connection) {        this.connection = connection;    }    public void run() {        try {            PreparedStatement statement = connection.prepareStatement("SELECT * FROM users");            ResultSet resultSet = statement.executeQuery();            while (resultSet.next()) {                // Process the results                String username = resultSet.getString("username");                System.out.println("Username: " + username);            }            resultSet.close();            statement.close();        } catch (SQLException e) {            e.printStackTrace();        }    }}public class Main {    public static void main(String[] args) throws SQLException {        String url = "jdbc:mysql://localhost:3306/mydatabase";        String username = "root";        String password = "password";        Connection connection = DriverManager.getConnection(url, username, password);        // Creating multiple threads for database access        for (int i = 0; i < 5; i++) {            Thread thread = new Thread(new DatabaseAccess(connection));            thread.start();        }        // Closing the database connection        connection.close();    }}

In the above example, a DatabaseAccess class is created to perform database access in a separate thread. Multiple threads are created to execute the database query concurrently. Each thread creates its own connection to the database and executes the query, processing the results as needed.

Related Article: How To Fix A NullPointerException In Java

Real World Example: User Interface Updates

Multithreading is essential for keeping user interfaces responsive while performing time-consuming operations in the background. Here's an example of using multithreading to update a user interface:

import javax.swing.*;import java.awt.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;class BackgroundTask implements Runnable {    private JTextArea textArea;    public BackgroundTask(JTextArea textArea) {        this.textArea = textArea;    }    public void run() {        // Simulating a time-consuming task        for (int i = 0; i < 10; i++) {            final int progress = i + 1;            // Updating the UI in the Event Dispatch Thread            SwingUtilities.invokeLater(() -> {                textArea.append("Task in progress: " + progress + "%\n");            });            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }        // Updating the UI after the task is completed        SwingUtilities.invokeLater(() -> {            textArea.append("Task completed.\n");        });    }}public class Main {    public static void main(String[] args) {        JFrame frame = new JFrame("Multithreading Example");        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);        frame.setSize(300, 200);        JTextArea textArea = new JTextArea();        textArea.setEditable(false);        frame.getContentPane().add(new JScrollPane(textArea), BorderLayout.CENTER);        JButton startButton = new JButton("Start Task");        startButton.addActionListener(new ActionListener() {            public void actionPerformed(ActionEvent e) {                // Creating a new thread for the background task                Thread thread = new Thread(new BackgroundTask(textArea));                thread.start();            }        });        frame.getContentPane().add(startButton, BorderLayout.SOUTH);        frame.setVisible(true);    }}

In the above example, a BackgroundTask class is created to simulate a time-consuming task. The task updates a JTextArea with progress information while running. The task is executed in a separate thread using the Thread class. The user interface remains responsive while the task is running, allowing the user to interact with other components.

Real World Example: Background Processing

Multithreading is often used for performing background processing tasks in Java applications. These tasks can include data processing, file handling, or any other operation that does not require immediate user interaction. Here's an example of using multithreading for background processing:

class BackgroundTask implements Runnable {    public void run() {        // Code for the background processing task    }}public class Main {    public static void main(String[] args) {        // Creating a new thread for the background task        Thread thread = new Thread(new BackgroundTask());        thread.start();        // Continue with other operations in the main thread    }}

In the above example, a BackgroundTask class is created to perform the background processing task. The task is executed in a separate thread using the Thread class. The main thread can continue with other operations while the background task is running, improving overall application responsiveness.

Spring Boot Integration with Payment, Communication Tools

Integrate Spring Boot seamlessly with popular payment gateways and communication tools like Stripe, PayPal, Twilio, and chatbots. Learn how to set up… read more

How to Print an ArrayList in Java

Printing an ArrayList in Java can be done in multiple ways. One approach is to use a for-each loop, which allows you to iterate through the elements … read more

How to Convert a String to Date in Java

This article provides a step-by-step guide on how to convert a string to a date in Java. It covers two approaches: using SimpleDateFormat and using D… read more

Java Constructor Tutorial

Java constructors are an essential part of object-oriented programming. They allow you to initialize objects and perform necessary setup tasks. This … read more

How to Initialize an ArrayList in One Line in Java

This article provides a simple guide on initializing an ArrayList in one line using Java. It covers different approaches like using the Arrays.asList… read more

Java Printf Method Tutorial

Learn how to use the Java Printf method for formatted output. This tutorial covers the syntax, arguments, flags, and best practices of the Printf met… read more

How To Convert Java Objects To JSON With Jackson

Java objects and JSON are commonly used in Java programming. If you need to convert Java objects to JSON, the Jackson library provides a convenient s… read more

How to Set the JAVA_HOME in Linux for All Users

Setting JAVA_HOME in Linux for all users can be achieved through two different methods. The first method involves setting JAVA_HOME in the /etc/envir… read more

How To Fix Java Certification Path Error

Ensure your web applications are secure with this article. Learn how to resolve the 'unable to find valid certification path to requested target' err… read more

Java Composition Tutorial

This tutorial: Learn how to use composition in Java with an example. This tutorial covers the basics of composition, its advantages over inheritance,… read more