java.util.concurrent.RejectedExecutionException: rejected from java.util.concurrent.ThreadPoolExecutor

The java.util.concurrent.RejectedExecutionException: rejected from java.util.concurrent.ThreadPoolExecutor error occurs when the thread pool queue is full and no further threads can be created in spring boot. If the ThreadPoolTaskExecutor could not create a thread or could not add it to the queue, the request will be dropped with rejected_thread_execution and the exception Task java.util.concurrent.FutureTask rejected from java.util.concurrent.ThreadPoolExecutor will be thrown.

The ThreadPoolTaskExecutor is created with default values in the spring boot application. The default core pool size is 8, the maximum pool size is MAX INTEGER, and the queue capacity size is MAX INTEGER. There will be 8 threads created in the default implementation, and all remaining threads will be queued. This exception will not occur if you use the default settings for ThreadPoolTaskExecutor.

If an inappropriate value is configured in the custom ThreadPoolTaskExecutor, the exception will be thrown. If the ThreadPoolTaskExecutor cannot create a thread and the queue is complete, the exception java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask rejected from java.util.concurrent.ThreadPoolExecutor will be thrown.



Exception

The sample stack trace of the exception java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask is shown as below. The thread will be rejected if it cannot created or queued.

2020-12-04 18:52:11.817 ERROR 568 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.core.task.TaskRejectedException: Executor [java.util.concurrent.ThreadPoolExecutor@6316ca27[Running, pool size = 4, active threads = 4, queued tasks = 5, completed tasks = 0]] did not accept task: org.springframework.aop.interceptor.AsyncExecutionInterceptor$$Lambda$524/1677840313@1e4bd090] with root cause

java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@453c221c rejected from java.util.concurrent.ThreadPoolExecutor@6316ca27[Running, pool size = 4, active threads = 4, queued tasks = 5, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047) ~[na:1.8.0_101]
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823) [na:1.8.0_101]
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369) [na:1.8.0_101]
	at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134) ~[na:1.8.0_101]
	at org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.submit(ThreadPoolTaskExecutor.java:348) ~[spring-context-5.3.1.jar:5.3.1]
	at org.springframework.aop.interceptor.AsyncExecutionAspectSupport.doSubmit(AsyncExecutionAspectSupport.java:290) ~[spring-aop-5.3.1.jar:5.3.1]
	at org.springframework.aop.interceptor.AsyncExecutionInterceptor.invoke(AsyncExecutionInterceptor.java:129) ~[spring-aop-5.3.1.jar:5.3.1]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.1.jar:5.3.1]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.3.1.jar:5.3.1]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691) ~[spring-aop-5.3.1.jar:5.3.1]
	at com.yawintutor.TestService$$EnhancerBySpringCGLIB$$20f93fbe.print(<generated>) ~[classes/:na]
	at com.yawintutor.TestController.welcome(TestController.java:24) ~[classes/:na]


How to reproduce this exception

If the inappropriate value is configured while creating custom ThreadPoolTaskExecutor in the spring boot application. The ThreadPoolTaskExecutor will execute as long as it can add in the queue or create a thread. If the queue is full and ThreadPoolTaskExecutor reached the max thread size, the exception will be thrown.

package com.yawintutor;

import java.util.concurrent.Executor;

import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

@Service
public class TestService {

	@Async ("ThreadPoolTaskExecutorBean")
	public void print(int index) {
		System.out.println("start: " + Thread.currentThread().getName() + ", index : " + index);
		try { Thread.sleep(5000);} catch(Exception e) {}
		System.out.println("end  : " + Thread.currentThread().getName() + ", index : " + index);
		throw new NullPointerException();
	}

	@Bean(name = "ThreadPoolTaskExecutorBean")
	public Executor getAsyncExecutor() {
		int corePoolSize = 3;
		int maxPoolSize = 4;
		int queueCapacity = 5;

		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(corePoolSize);
		executor.setMaxPoolSize(maxPoolSize);
		executor.setQueueCapacity(queueCapacity);
		executor.initialize();
		return executor;
	}
}
package com.yawintutor;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

	@Autowired
	TestService testService;
	
	@RequestMapping(value = "/welcome", method = RequestMethod.GET)
	public String welcome() {
		for(int i=0; i<10; i++) {
			testService.print(i);
		}
		return "success";
	}

}

Output

If the rest url http://localhost:8080/welcome is invoked from a browser window, the rest controller will create 10 threads. Since all 10 threads could not be created or queued, this exception ” java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask” will be thrown.

2020-12-04 18:52:11.817 ERROR 568 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.core.task.TaskRejectedException: Executor [java.util.concurrent.ThreadPoolExecutor@6316ca27[Running, pool size = 4, active threads = 4, queued tasks = 5, completed tasks = 0]] did not accept task: org.springframework.aop.interceptor.AsyncExecutionInterceptor$$Lambda$524/1677840313@1e4bd090] with root cause

java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@453c221c rejected from java.util.concurrent.ThreadPoolExecutor@6316ca27[Running, pool size = 4, active threads = 4, queued tasks = 5, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047) ~[na:1.8.0_101]
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823) [na:1.8.0_101]
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369) [na:1.8.0_101]
	at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134) ~[na:1.8.0_101]
	at org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.submit(ThreadPoolTaskExecutor.java:348) ~[spring-context-5.3.1.jar:5.3.1]
	at org.springframework.aop.interceptor.AsyncExecutionAspectSupport.doSubmit(AsyncExecutionAspectSupport.java:290) ~[spring-aop-5.3.1.jar:5.3.1]
	at org.springframework.aop.interceptor.AsyncExecutionInterceptor.invoke(AsyncExecutionInterceptor.java:129) ~[spring-aop-5.3.1.jar:5.3.1]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.1.jar:5.3.1]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.3.1.jar:5.3.1]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691) ~[spring-aop-5.3.1.jar:5.3.1]
	at com.yawintutor.TestService$$EnhancerBySpringCGLIB$$20f93fbe.print(<generated>) ~[classes/:na]
	at com.yawintutor.TestController.welcome(TestController.java:24) ~[classes/:na]


Root Cause

The exception RejectedExecutionException: Task java.util.concurrent.FutureTask will be thrown for the following reasons

  • The thread pool is shut down before the thread is processed.
  • The thread pool queue is complete and no further threads can be created.

If the below condition is matched, the exception will be thrown in the ThreadPoolTaskExecutor

No of threads created > ( queue capacity size + max pool size)


Solution 1

If the number of requests increases and the system CPU utilisation is high, the max thread size cannot be increased. In this case, the size of the queue should be increased to hold more threads. The queue threads will be executed when the request is reduced. In the example below the queue capacity is increased in order to avoid an exception java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask.

TestService.java

@Bean(name = "ThreadPoolTaskExecutorBean")
	public Executor getAsyncExecutor() {
		int corePoolSize = 3;
		int maxPoolSize = 4;
		int queueCapacity = 10;

		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(corePoolSize);
		executor.setMaxPoolSize(maxPoolSize);
		executor.setQueueCapacity(queueCapacity);
		executor.initialize();
		return executor;
	}


Solution 2

If the request increases in a few scenarios and the load request is normal for the rest of the time, the size of the queue capacity cannot be increased. It occupies the memory in an unnecessary way. In this case, the maximum size of the pool could be increased. If the load of the request is increased and the queue is full, new additional threads will be created. The request will therefore be processed more quickly. Therefore the size of the queue will decrease immediately. After the load of the request is reduced, the additional threads will expire automatically.

TestService.java

	@Bean(name = "ThreadPoolTaskExecutorBean")
	public Executor getAsyncExecutor() {
		int corePoolSize = 3;
		int maxPoolSize = 5;
		int queueCapacity = 5;

		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(corePoolSize);
		executor.setMaxPoolSize(maxPoolSize);
		executor.setQueueCapacity(queueCapacity);
		executor.initialize();
		return executor;
	}


Solution 3

If neither queue capacity size or maximum pool size is increased and the request load increases, the exception java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask will be thrown. In this case, the request load should be optimized till the current thread is completed. The request load should be delayed to execute in the thread.

TestController.java

package com.yawintutor;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

	@Autowired
	TestService testService;
	
	@RequestMapping(value = "/welcome", method = RequestMethod.GET)
	public String welcome() {
		for(int i=0; i<10; i++) {
			testService.print(i);
			try { Thread.sleep(1000);} catch(Exception e) {}
		}
		return "success";
	}

}



Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *