Resolving 'java.util.concurrent.TimeoutException' Errors in Java

Abdul D.
jump to solution

The Problem

In Java, you may run into a java.util.concurrent.TimeoutException error when using the java.util.concurrent package:

Task timed out: null                                                                                              
java.util.concurrent.TimeoutException                                                                             
        at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:204)
        at TimeoutExceptionExample.main(TimeoutExceptionExample.java:17)

The Solution

You can resolve a java.util.concurrent.TimeoutException error by ensuring the task causing the error does not exceed its timeout limit or by returning a default value when the limit is reached.

The TimeoutException indicates that the error was thrown by the get method of FutureTask. Specifically, it was thrown by the overloaded version of the get method that accepts a timeout parameter, as shown in the following line from the stack trace:

at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:204)

This design ensures that the task can be controlled and prevented from blocking indefinitely. However, if the specified timeout is too short or the task encounters delays, a TimeoutException error occurs.

Let’s use the following example to reproduce and resolve the issue:

import java.util.concurrent.*;

public class TimeoutExceptionExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        Future<Integer> future = executorService.submit(() -> {
            int sum = 0;
            for (int i = 1; i <= 1000; i++) {   // Add numbers from 1 to 1000
                Thread.sleep(1);                // Simulate a delay for each addition
                sum += i;
            }
            return sum;
        });

        try {
            Integer result = future.get(1, TimeUnit.SECONDS);
            System.out.println("Sum: " + result);
        } catch (TimeoutException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}

This code calculates the sum of numbers from 1 to 1000. It simulates a delay for each addition, which causes the task to exceed the timeout limit.

Optimize the Task

First things first: Get rid of any inefficiencies.

The delay in the loop (Thread.sleep(1)) causes the task to take longer than expected. Remove the delay to optimize the code:

import java.util.concurrent.*;

public class OptimizedTimeoutExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        Future<Integer> future = executorService.submit(() -> {
            int sum = 0;
            for (int i = 1; i <= 1000; i++) {
                sum += i; // Perform addition without delay.
            }
            return sum;
        });

        try {
            Integer result = future.get(1, TimeUnit.SECONDS); 
            System.out.println("Sum: " + result);
        } catch (TimeoutException e) {
            System.err.println("Task timed out: " + e.getMessage());
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}

This prints:

Sum: 500500   

Increase the Timeout

When optimization isn’t an option, give the task more time to complete:

import java.util.concurrent.*;

public class TimeoutExceptionExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        Future<Integer> future = executorService.submit(() -> {
            int sum = 0;
            for (int i = 1; i <= 1000; i++) {
                sum += i; 
                Thread.sleep(1); 
            }
            return sum;
        });

        try {
            Integer result = future.get(3, TimeUnit.SECONDS); // Extend the timeout limit to 3 seconds
            System.out.println("Sum: " + result);
        } catch (TimeoutException e) {
            System.err.println("Task timed out: " + e.getMessage());
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}

This prints:

Sum: 500500

Handle the Timeout Gracefully

If extending the timeout still doesn’t work, you can use a fallback mechanism to handle the error.

This example returns a default value when the task times out:

import java.util.concurrent.*;

public class FallbackTimeoutExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        Future<Integer> future = executorService.submit(() -> {
            int sum = 0;
            for (int i = 1; i <= 1000000; i++) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                sum += i;
            }
            return sum;
        });

        try {
            Integer result = future.get(1, TimeUnit.SECONDS);
            System.out.println("Sum: " + result);
        } catch (TimeoutException e) {
            System.err.println("Task timed out. Returning default value.");
            System.out.println("Sum: 0 (Default value)");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}

This prints:

Fallback: Task timed out. Returning default value.
Sum: 0 (Default value)

Use Asynchronous Processing for the Timeout

Let’s get fancy and use the CompletableFuture method to define a default value in case of a timeout:

import java.util.concurrent.*;

public class CompletableFutureExample {
    public static void main(String[] args) {
        // This runs the task asynchronously.
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            int sum = 0; 
            for (int i = 1; i <= 1000000; i++) {
                sum += i;
                try {
                    Thread.sleep(1); // Simulate a delay
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            return sum;
        });

        // If the task does not complete within 1 second, return the default value 1784293664.
        Integer result = future.completeOnTimeout(1784293664, 1, TimeUnit.SECONDS).join(); 
        
        System.out.println("Sum: " + result);
    }
}

This prints:

Sum: 1784293664

Note

The Future interface provides two versions of the get method:

  • get() waits indefinitely for the task to complete.
  • get(long timeout, TimeUnit unit) waits up to the specified timeout for the task to complete, throwing a TimeoutException if the timeout is exceeded.

While using the parameterless get() avoids the TimeoutException` error, it can result in the program waiting indefinitely, which is not ideal.

Further Reading

How to Write to a File in Java
Lewis D.
How to convert an array to a List in Java?
Abdul D.
Java Jakarta Class Not Found
Venter C.

Considered "not bad" by 4 million developers and more than 150,000 organizations worldwide, Sentry provides code-level observability to many of the world's best-known companies like Disney, Peloton, Cloudflare, Eventbrite, Slack, Supercell, and Rockstar Games. Each month we process billions of exceptions from the most popular products on the internet.

Sentry