• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TAKETODAY / today-infrastructure / 18154768944

01 Oct 2025 07:26AM UTC coverage: 81.882% (-0.005%) from 81.887%
18154768944

push

github

web-flow
Merge pull request #290 from TAKETODAY/dev/jspecify

jspecify

59788 of 78013 branches covered (76.64%)

Branch coverage included in aggregate %.

141239 of 167496 relevant lines covered (84.32%)

3.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.08
today-context/src/main/java/infra/scheduling/support/ThreadPoolTaskExecutorBuilder.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.scheduling.support;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.time.Duration;
23
import java.util.Arrays;
24
import java.util.Collections;
25
import java.util.LinkedHashSet;
26
import java.util.Set;
27

28
import infra.beans.BeanUtils;
29
import infra.core.task.TaskDecorator;
30
import infra.lang.Assert;
31
import infra.scheduling.concurrent.ThreadPoolTaskExecutor;
32
import infra.util.CollectionUtils;
33
import infra.util.StringUtils;
34

35
/**
36
 * Builder that can be used to configure and create a {@link ThreadPoolTaskExecutor}.
37
 * Provides convenience methods to set common {@link ThreadPoolTaskExecutor} settings and
38
 * register {@link #taskDecorator(TaskDecorator)}). For advanced configuration, consider
39
 * using {@link ThreadPoolTaskExecutorCustomizer}.
40
 * <p>
41
 * In a typical auto-configured Infra application this builder is available as a
42
 * bean and can be injected whenever a {@link ThreadPoolTaskExecutor} is needed.
43
 *
44
 * @author Stephane Nicoll
45
 * @author Filip Hrisafov
46
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
47
 * @since 4.0
48
 */
49
public class ThreadPoolTaskExecutorBuilder {
50

51
  @Nullable
52
  private final Integer queueCapacity;
53

54
  @Nullable
55
  private final Integer corePoolSize;
56

57
  @Nullable
58
  private final Integer maxPoolSize;
59

60
  @Nullable
61
  private final Boolean allowCoreThreadTimeOut;
62

63
  @Nullable
64
  private final Duration keepAlive;
65

66
  @Nullable
67
  private final Boolean acceptTasksAfterContextClose;
68

69
  @Nullable
70
  private final Boolean awaitTermination;
71

72
  @Nullable
73
  private final Duration awaitTerminationPeriod;
74

75
  @Nullable
76
  private final String threadNamePrefix;
77

78
  @Nullable
79
  private final TaskDecorator taskDecorator;
80

81
  @Nullable
82
  private final Set<ThreadPoolTaskExecutorCustomizer> customizers;
83

84
  public ThreadPoolTaskExecutorBuilder() {
2✔
85
    this.queueCapacity = null;
3✔
86
    this.corePoolSize = null;
3✔
87
    this.maxPoolSize = null;
3✔
88
    this.allowCoreThreadTimeOut = null;
3✔
89
    this.keepAlive = null;
3✔
90
    this.awaitTermination = null;
3✔
91
    this.awaitTerminationPeriod = null;
3✔
92
    this.threadNamePrefix = null;
3✔
93
    this.taskDecorator = null;
3✔
94
    this.customizers = null;
3✔
95
    this.acceptTasksAfterContextClose = null;
3✔
96
  }
1✔
97

98
  private ThreadPoolTaskExecutorBuilder(@Nullable Integer queueCapacity, @Nullable Integer corePoolSize, @Nullable Integer maxPoolSize,
99
          @Nullable Boolean allowCoreThreadTimeOut, @Nullable Duration keepAlive, @Nullable Boolean acceptTasksAfterContextClose,
100
          @Nullable Boolean awaitTermination, @Nullable Duration awaitTerminationPeriod, @Nullable String threadNamePrefix,
101
          @Nullable TaskDecorator taskDecorator, @Nullable Set<ThreadPoolTaskExecutorCustomizer> customizers) {
2✔
102
    this.queueCapacity = queueCapacity;
3✔
103
    this.corePoolSize = corePoolSize;
3✔
104
    this.maxPoolSize = maxPoolSize;
3✔
105
    this.allowCoreThreadTimeOut = allowCoreThreadTimeOut;
3✔
106
    this.keepAlive = keepAlive;
3✔
107
    this.acceptTasksAfterContextClose = acceptTasksAfterContextClose;
3✔
108
    this.awaitTermination = awaitTermination;
3✔
109
    this.awaitTerminationPeriod = awaitTerminationPeriod;
3✔
110
    this.threadNamePrefix = threadNamePrefix;
3✔
111
    this.taskDecorator = taskDecorator;
3✔
112
    this.customizers = customizers;
3✔
113
  }
1✔
114

115
  /**
116
   * Set the capacity of the queue. An unbounded capacity does not increase the pool and
117
   * therefore ignores {@link #maxPoolSize(int) maxPoolSize}.
118
   *
119
   * @param queueCapacity the queue capacity to set
120
   * @return a new builder instance
121
   */
122
  public ThreadPoolTaskExecutorBuilder queueCapacity(int queueCapacity) {
123
    return new ThreadPoolTaskExecutorBuilder(queueCapacity, this.corePoolSize, this.maxPoolSize,
26✔
124
            this.allowCoreThreadTimeOut, this.keepAlive, acceptTasksAfterContextClose, this.awaitTermination,
125
            this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, this.customizers);
126
  }
127

128
  /**
129
   * Set the core number of threads. Effectively that maximum number of threads as long
130
   * as the queue is not full.
131
   * <p>
132
   * Core threads can grow and shrink if {@link #allowCoreThreadTimeOut(boolean)} is
133
   * enabled.
134
   *
135
   * @param corePoolSize the core pool size to set
136
   * @return a new builder instance
137
   */
138
  public ThreadPoolTaskExecutorBuilder corePoolSize(int corePoolSize) {
139
    return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, corePoolSize, this.maxPoolSize,
26✔
140
            this.allowCoreThreadTimeOut, this.keepAlive, acceptTasksAfterContextClose, this.awaitTermination,
141
            this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, this.customizers);
142
  }
143

144
  /**
145
   * Set the maximum allowed number of threads. When the {@link #queueCapacity(int)
146
   * queue} is full, the pool can expand up to that size to accommodate the load.
147
   * <p>
148
   * If the {@link #queueCapacity(int) queue capacity} is unbounded, this setting is
149
   * ignored.
150
   *
151
   * @param maxPoolSize the max pool size to set
152
   * @return a new builder instance
153
   */
154
  public ThreadPoolTaskExecutorBuilder maxPoolSize(int maxPoolSize) {
155
    return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, maxPoolSize,
26✔
156
            this.allowCoreThreadTimeOut, this.keepAlive, acceptTasksAfterContextClose, this.awaitTermination, this.awaitTerminationPeriod,
157
            this.threadNamePrefix, this.taskDecorator, this.customizers);
158
  }
159

160
  /**
161
   * Set whether core threads are allowed to time out. When enabled, this enables
162
   * dynamic growing and shrinking of the pool.
163
   *
164
   * @param allowCoreThreadTimeOut if core threads are allowed to time out
165
   * @return a new builder instance
166
   */
167
  public ThreadPoolTaskExecutorBuilder allowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) {
168
    return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize,
10✔
169
            allowCoreThreadTimeOut, this.keepAlive, acceptTasksAfterContextClose, this.awaitTermination,
16✔
170
            this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, this.customizers);
171
  }
172

173
  /**
174
   * Set the time limit for which threads may remain idle before being terminated.
175
   *
176
   * @param keepAlive the keep alive to set
177
   * @return a new builder instance
178
   */
179
  public ThreadPoolTaskExecutorBuilder keepAlive(@Nullable Duration keepAlive) {
180
    return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize,
25✔
181
            this.allowCoreThreadTimeOut, keepAlive, acceptTasksAfterContextClose, this.awaitTermination,
182
            this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, this.customizers);
183
  }
184

185
  /**
186
   * Set whether the executor should wait for scheduled tasks to complete on shutdown,
187
   * not interrupting running tasks and executing all tasks in the queue.
188
   *
189
   * @param awaitTermination whether the executor needs to wait for the tasks to
190
   * complete on shutdown
191
   * @return a new builder instance
192
   * @see #awaitTerminationPeriod(Duration)
193
   */
194
  public ThreadPoolTaskExecutorBuilder awaitTermination(boolean awaitTermination) {
195
    return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize,
16✔
196
            this.allowCoreThreadTimeOut, this.keepAlive, acceptTasksAfterContextClose, awaitTermination,
10✔
197
            this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, this.customizers);
198
  }
199

200
  /**
201
   * Set the maximum time the executor is supposed to block on shutdown. When set, the
202
   * executor blocks on shutdown in order to wait for remaining tasks to complete their
203
   * execution before the rest of the container continues to shut down. This is
204
   * particularly useful if your remaining tasks are likely to need access to other
205
   * resources that are also managed by the container.
206
   *
207
   * @param awaitTerminationPeriod the await termination period to set
208
   * @return a new builder instance
209
   */
210
  public ThreadPoolTaskExecutorBuilder awaitTerminationPeriod(@Nullable Duration awaitTerminationPeriod) {
211
    return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize,
25✔
212
            this.allowCoreThreadTimeOut, this.keepAlive, acceptTasksAfterContextClose, this.awaitTermination,
213
            awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, this.customizers);
214
  }
215

216
  /**
217
   * Set whether to accept further tasks after the application context close phase has
218
   * begun.
219
   *
220
   * @param acceptTasksAfterContextClose whether to accept further tasks after the
221
   * application context close phase has begun
222
   * @return a new builder instance
223
   */
224
  public ThreadPoolTaskExecutorBuilder acceptTasksAfterContextClose(boolean acceptTasksAfterContextClose) {
225
    return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize,
14✔
226
            this.allowCoreThreadTimeOut, this.keepAlive, acceptTasksAfterContextClose, this.awaitTermination,
12✔
227
            this.awaitTerminationPeriod, this.threadNamePrefix, this.taskDecorator, this.customizers);
228
  }
229

230
  /**
231
   * Set the prefix to use for the names of newly created threads.
232
   *
233
   * @param threadNamePrefix the thread name prefix to set
234
   * @return a new builder instance
235
   */
236
  public ThreadPoolTaskExecutorBuilder threadNamePrefix(@Nullable String threadNamePrefix) {
237
    return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize,
25✔
238
            this.allowCoreThreadTimeOut, this.keepAlive, acceptTasksAfterContextClose, this.awaitTermination,
239
            this.awaitTerminationPeriod, threadNamePrefix, this.taskDecorator, this.customizers);
240
  }
241

242
  /**
243
   * Set the {@link TaskDecorator} to use or {@code null} to not use any.
244
   *
245
   * @param taskDecorator the task decorator to use
246
   * @return a new builder instance
247
   */
248
  public ThreadPoolTaskExecutorBuilder taskDecorator(@Nullable TaskDecorator taskDecorator) {
249
    return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize,
25✔
250
            this.allowCoreThreadTimeOut, this.keepAlive, acceptTasksAfterContextClose, this.awaitTermination,
251
            this.awaitTerminationPeriod, this.threadNamePrefix, taskDecorator, this.customizers);
252
  }
253

254
  /**
255
   * Set the {@link ThreadPoolTaskExecutorCustomizer ThreadPoolTaskExecutorCustomizers}
256
   * that should be applied to the {@link ThreadPoolTaskExecutor}. Customizers are
257
   * applied in the order that they were added after builder configuration has been
258
   * applied. Setting this value will replace any previously configured customizers.
259
   *
260
   * @param customizers the customizers to set
261
   * @return a new builder instance
262
   * @see #additionalCustomizers(ThreadPoolTaskExecutorCustomizer...)
263
   */
264
  public ThreadPoolTaskExecutorBuilder customizers(ThreadPoolTaskExecutorCustomizer... customizers) {
265
    Assert.notNull(customizers, "Customizers is required");
3✔
266
    return customizers(Arrays.asList(customizers));
5✔
267
  }
268

269
  /**
270
   * Set the {@link ThreadPoolTaskExecutorCustomizer ThreadPoolTaskExecutorCustomizers}
271
   * that should be applied to the {@link ThreadPoolTaskExecutor}. Customizers are
272
   * applied in the order that they were added after builder configuration has been
273
   * applied. Setting this value will replace any previously configured customizers.
274
   *
275
   * @param customizers the customizers to set
276
   * @return a new builder instance
277
   * @see #additionalCustomizers(ThreadPoolTaskExecutorCustomizer...)
278
   */
279
  public ThreadPoolTaskExecutorBuilder customizers(Iterable<? extends ThreadPoolTaskExecutorCustomizer> customizers) {
280
    Assert.notNull(customizers, "Customizers is required");
3✔
281
    return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize,
26✔
282
            this.allowCoreThreadTimeOut, this.keepAlive, acceptTasksAfterContextClose, this.awaitTermination, this.awaitTerminationPeriod,
283
            this.threadNamePrefix, this.taskDecorator, append(null, customizers));
2✔
284
  }
285

286
  /**
287
   * Add {@link ThreadPoolTaskExecutorCustomizer ThreadPoolTaskExecutorCustomizers} that
288
   * should be applied to the {@link ThreadPoolTaskExecutor}. Customizers are applied in
289
   * the order that they were added after builder configuration has been applied.
290
   *
291
   * @param customizers the customizers to add
292
   * @return a new builder instance
293
   * @see #customizers(ThreadPoolTaskExecutorCustomizer...)
294
   */
295
  public ThreadPoolTaskExecutorBuilder additionalCustomizers(ThreadPoolTaskExecutorCustomizer... customizers) {
296
    Assert.notNull(customizers, "Customizers is required");
3✔
297
    return additionalCustomizers(Arrays.asList(customizers));
5✔
298
  }
299

300
  /**
301
   * Add {@link ThreadPoolTaskExecutorCustomizer ThreadPoolTaskExecutorCustomizers} that
302
   * should be applied to the {@link ThreadPoolTaskExecutor}. Customizers are applied in
303
   * the order that they were added after builder configuration has been applied.
304
   *
305
   * @param customizers the customizers to add
306
   * @return a new builder instance
307
   * @see #customizers(ThreadPoolTaskExecutorCustomizer...)
308
   */
309
  public ThreadPoolTaskExecutorBuilder additionalCustomizers(Iterable<? extends ThreadPoolTaskExecutorCustomizer> customizers) {
310
    Assert.notNull(customizers, "Customizers is required");
3✔
311
    return new ThreadPoolTaskExecutorBuilder(this.queueCapacity, this.corePoolSize, this.maxPoolSize,
27✔
312
            this.allowCoreThreadTimeOut, this.keepAlive, acceptTasksAfterContextClose, this.awaitTermination, this.awaitTerminationPeriod,
313
            this.threadNamePrefix, this.taskDecorator, append(this.customizers, customizers));
2✔
314
  }
315

316
  /**
317
   * Build a new {@link ThreadPoolTaskExecutor} instance and configure it using this
318
   * builder.
319
   *
320
   * @return a configured {@link ThreadPoolTaskExecutor} instance.
321
   * @see #build(Class)
322
   * @see #configure(ThreadPoolTaskExecutor)
323
   */
324
  public ThreadPoolTaskExecutor build() {
325
    return configure(new ThreadPoolTaskExecutor());
6✔
326
  }
327

328
  /**
329
   * Build a new {@link ThreadPoolTaskExecutor} instance of the specified type and
330
   * configure it using this builder.
331
   *
332
   * @param <T> the type of task executor
333
   * @param taskExecutorClass the template type to create
334
   * @return a configured {@link ThreadPoolTaskExecutor} instance.
335
   * @see #build()
336
   * @see #configure(ThreadPoolTaskExecutor)
337
   */
338
  public <T extends ThreadPoolTaskExecutor> T build(Class<T> taskExecutorClass) {
339
    return configure(BeanUtils.newInstance(taskExecutorClass));
×
340
  }
341

342
  /**
343
   * Configure the provided {@link ThreadPoolTaskExecutor} instance using this builder.
344
   *
345
   * @param <T> the type of task executor
346
   * @param taskExecutor the {@link ThreadPoolTaskExecutor} to configure
347
   * @return the task executor instance
348
   * @see #build()
349
   * @see #build(Class)
350
   */
351
  public <T extends ThreadPoolTaskExecutor> T configure(T taskExecutor) {
352
    if (queueCapacity != null) {
3✔
353
      taskExecutor.setQueueCapacity(queueCapacity);
5✔
354
    }
355

356
    if (corePoolSize != null) {
3✔
357
      taskExecutor.setCorePoolSize(corePoolSize);
5✔
358
    }
359

360
    if (maxPoolSize != null) {
3✔
361
      taskExecutor.setMaxPoolSize(maxPoolSize);
5✔
362
    }
363
    if (keepAlive != null) {
3✔
364
      taskExecutor.setKeepAliveSeconds((int) keepAlive.getSeconds());
6✔
365
    }
366
    if (allowCoreThreadTimeOut != null) {
3✔
367
      taskExecutor.setAllowCoreThreadTimeOut(allowCoreThreadTimeOut);
5✔
368
    }
369

370
    if (awaitTermination != null) {
3✔
371
      taskExecutor.setWaitForTasksToCompleteOnShutdown(awaitTermination);
5✔
372
    }
373
    if (awaitTerminationPeriod != null) {
3✔
374
      taskExecutor.setAwaitTerminationMillis(awaitTerminationPeriod.toMillis());
5✔
375
    }
376
    if (StringUtils.hasText(threadNamePrefix)) {
4✔
377
      taskExecutor.setThreadNamePrefix(threadNamePrefix);
4✔
378
    }
379
    if (taskDecorator != null) {
3✔
380
      taskExecutor.setTaskDecorator(taskDecorator);
4✔
381
    }
382
    if (acceptTasksAfterContextClose != null) {
3✔
383
      taskExecutor.setAcceptTasksAfterContextClose(acceptTasksAfterContextClose);
5✔
384
    }
385

386
    if (CollectionUtils.isNotEmpty(customizers)) {
4✔
387
      for (ThreadPoolTaskExecutorCustomizer customizer : customizers) {
11✔
388
        customizer.customize(taskExecutor);
3✔
389
      }
1✔
390
    }
391
    return taskExecutor;
2✔
392
  }
393

394
  private <T> Set<T> append(@Nullable Set<T> set, Iterable<? extends T> additions) {
395
    LinkedHashSet<T> result = new LinkedHashSet<>((set != null) ? set : Collections.emptySet());
9✔
396
    for (T addition : additions) {
9✔
397
      result.add(addition);
4✔
398
    }
1✔
399
    return Collections.unmodifiableSet(result);
3✔
400
  }
401

402
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc