eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

eBook – Java Concurrency – NPI (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

1. Introduction

In this tutorial, we’re going to explore different ways to start a thread and execute parallel tasks.

This is very useful, in particular when dealing with long or recurring operations that can’t run on the main thread, or where the UI interaction can’t be put on hold while waiting for the operation’s results.

To learn more about the details of threads, definitely read our tutorial about the Life Cycle of a Thread in Java.

2. The Basics of Running a Thread

We can easily write some logic that runs in a parallel thread by using the Thread framework.

Let’s try a basic example, by extending the Thread class:

public class NewThread extends Thread {
    public void run() {
        long startTime = System.currentTimeMillis();
        int i = 0;
        while (true) {
            System.out.println(this.getName() + ": New Thread is running..." + i++);
            try {
                //Wait for one sec so it doesn't print too fast
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ...
        }
    }
}

And now we write a second class to initialize and start our thread:

public class SingleThreadExample {
    public static void main(String[] args) {
        NewThread t = new NewThread();
        t.start();
    }
}

We should call the start() method on threads in the NEW state (the equivalent of not started). Otherwise, Java will throw an instance of IllegalThreadStateException exception.

Now let’s assume we need to start multiple threads:

public class MultipleThreadsExample {
    public static void main(String[] args) {
        NewThread t1 = new NewThread();
        t1.setName("MyThread-1");
        NewThread t2 = new NewThread();
        t2.setName("MyThread-2");
        t1.start();
        t2.start();
    }
}

Our code still looks quite simple and very similar to the examples we can find online.

Of course, this is far from production-ready code, where it’s of critical importance to manage resources in the correct way, to avoid too much context switching or too much memory usage.

So, to get production-ready we now need to write additional boilerplate to deal with:

  • the consistent creation of new threads
  • the number of concurrent live threads
  • the threads deallocation: very important for daemon threads in order to avoid leaks

If we want to, we can write our own code for all these case scenarios and even some more, but why should we reinvent the wheel?

3. The ExecutorService Framework

The ExecutorService implements the Thread Pool design pattern (also called a replicated worker or worker-crew model) and takes care of the thread management we mentioned above, plus it adds some very useful features like thread reusability and task queues.

Thread reusability, in particular, is very important: in a large-scale application, allocating and deallocating many thread objects creates a significant memory management overhead.

With worker threads, we minimize the overhead caused by thread creation.

To ease the pool configuration, ExecutorService comes with an easy constructor and some customization options, such as the type of queue, the minimum and the maximum number of threads and their naming convention.

For more details about the ExecutorService, please read our Guide to the Java ExecutorService.

4. Starting a Task with Executors

Thanks to this powerful framework, we can switch our mindset from starting threads to submitting tasks.

Let’s look at how we can submit an asynchronous task to our executor:

ExecutorService executor = Executors.newFixedThreadPool(10);
...
executor.submit(() -> {
    new Task();
});

There are two methods we can use: execute, which returns nothing, and submit, which returns a Future encapsulating the computation’s result.

For more information about Futures, please read our Guide to java.util.concurrent.Future.

5. Starting a Task with CompletableFutures

To retrieve the final result from a Future object we can use the get method available in the object, but this would block the parent thread until the end of the computation.

Alternatively, we could avoid the block by adding more logic to our task, but we have to increase the complexity of our code.

Java 1.8 introduced a new framework on top of the Future construct to better work with the computation’s result: the CompletableFuture.

CompletableFuture implements CompletableStage, which adds a vast selection of methods to attach callbacks and avoid all the plumbing needed to run operations on the result after it’s ready.

The implementation to submit a task is a lot simpler:

CompletableFuture.supplyAsync(() -> "Hello");

supplyAsync takes a Supplier containing the code we want to execute asynchronously — in our case the lambda parameter.

The task is now implicitly submitted to the ForkJoinPool.commonPool(), or we can specify the Executor we prefer as a second parameter.

To know more about CompletableFuture, please read our Guide To CompletableFuture.

6. Running Delayed or Periodic Tasks

When working with complex web applications, we may need to run tasks at specific times, maybe regularly.

Java has few tools that can help us to run delayed or recurring operations:

  • java.util.Timer
  • java.util.concurrent.ScheduledThreadPoolExecutor

6.1. Timer

Timer is a facility to schedule tasks for future execution in a background thread.

Tasks may be scheduled for one-time execution, or for repeated execution at regular intervals.

Let’s see what the code looks if we want to run a task after one second of delay:

TimerTask task = new TimerTask() {
    public void run() {
        System.out.println("Task performed on: " + new Date() + "n" 
          + "Thread's name: " + Thread.currentThread().getName());
    }
};
Timer timer = new Timer("Timer");
long delay = 1000L;
timer.schedule(task, delay);

Now let’s add a recurring schedule:

timer.scheduleAtFixedRate(repeatedTask, delay, period);

This time, the task will run after the delay specified and it’ll be recurrent after the period of time passed.

For more information, please read our guide to Java Timer.

6.2. ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor has methods similar to the Timer class:

ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
ScheduledFuture<Object> resultFuture
  = executorService.schedule(callableTask, 1, TimeUnit.SECONDS);

To end our example, we use scheduleAtFixedRate() for recurring tasks:

ScheduledFuture<Object> resultFuture
 = executorService.scheduleAtFixedRate(runnableTask, 100, 450, TimeUnit.MILLISECONDS);

The code above will execute a task after an initial delay of 100 milliseconds, and after that, it’ll execute the same task every 450 milliseconds.

If the processor can’t finish processing the task in time before the next occurrence, the ScheduledExecutorService will wait until the current task is completed, before starting the next.

To avoid this waiting time, we can use scheduleWithFixedDelay(), which, as described by its name, guarantees a fixed length delay between iterations of the task.

For more details about ScheduledExecutorService, please read our Guide to the Java ExecutorService.

6.3. Which Tool Is Better?

If we run the examples above, the computation’s result looks the same.

So, how do we choose the right tool?

When a framework offers multiple choices, it’s important to understand the underlying technology to make an informed decision.

Let’s try to dive a bit deeper under the hood.

Timer:

  • does not offer real-time guarantees: it schedules tasks using the Object.wait(long) method
  • there’s a single background thread, so tasks run sequentially and a long-running task can delay others
  • runtime exceptions thrown in a TimerTask would kill the only thread available, thus killing Timer

ScheduledThreadPoolExecutor:

  • can be configured with any number of threads
  • can take advantage of all available CPU cores
  • catches runtime exceptions and lets us handle them if we want to (by overriding afterExecute method from ThreadPoolExecutor)
  • cancels the task that threw the exception, while letting others continue to run
  • relies on the OS scheduling system to keep track of time zones, delays, solar time, etc.
  • provides collaborative API if we need coordination between multiple tasks, like waiting for the completion of all tasks submitted
  • provides better API for management of the thread life cycle

The choice now is obvious, right?

7. Difference Between Future and ScheduledFuture

In our code examples, we can observe that ScheduledThreadPoolExecutor returns a specific type of Future: ScheduledFuture.

ScheduledFuture extends both Future and Delayed interfaces, thus inheriting the additional method getDelay that returns the remaining delay associated with the current task. It’s extended by RunnableScheduledFuture that adds a method to check if the task is periodic.

ScheduledThreadPoolExecutor implements all these constructs through the inner class ScheduledFutureTask and uses them to control the task life cycle.

8. Conclusions

In this tutorial, we experimented with the different frameworks available to start threads and run tasks in parallel.

Then, we went deeper into the differences between Timer and ScheduledThreadPoolExecutor.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook – Java Concurrency – NPI (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook Jackson – NPI EA – 3 (cat = Jackson)