javascheduledexecutorservicetry-with-resources

ScheduledExecutorService doesn't work in “try with resources” block


In my Java 23 project, IntelliJ IDEA soft-complained that I was calling Executors.newScheduledThreadPool(1) “without a 'try'-with-resources statement”. To fix this, I accepted IntelliJ's suggestion to surround it with such a try-with-resources block:

@Test
public void testScheduledExecutorServiceTryWithResources() throws InterruptedException {
    Logger LOG = LogManager.getLogger();

    try (ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1)) {
        executorService.scheduleWithFixedDelay(() -> LOG.info("Doing something …"), 0, 1, TimeUnit.SECONDS);
    } catch(Exception e) {
        LOG.error("testScheduledExecutorService(): Error occurred: {}", e.getMessage(), e);
    }

    Thread.sleep(5000);
    LOG.info("Finishing at {}", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
}

However, this does not work. Here's the console output:

Finishing at 2025-03-03T19:38:08.549627875

Using the ScheduledExecutorService without a try-with-resources block works fine, however:

@Test
public void testScheduledExecutorServiceBare() throws InterruptedException {
    Logger LOG = LogManager.getLogger();

    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    executorService.scheduleWithFixedDelay(() -> LOG.info("Doing something …"), 0, 1, TimeUnit.SECONDS);

    Thread.sleep(5000);
    LOG.info("Finishing at {}", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
}

Here's the console output:

Doing something …
Doing something …
Doing something …
Doing something …
Doing something …
Doing something …
Finishing at 2025-03-03T19:30:45.165711955

Why doesn't the try-with-resources block work?


Solution

  • In my Java 23 project, IntelliJ IDEA soft-complained that I was calling Executors.newScheduledThreadPool(1) “without a 'try'-with-resources statement”.

    No doubt that diagnostic is based on a general rule that AutoCloseable objects such as ExecutorServices should usually be used in a try-with-resources statement. Wrapping new instances that way ensures that they are in fact closed when no longer needed. IntelliJ surely is not expressing a rule specifically about ScheduledExecutorService.

    Why doesn't the try-with-resources block work?

    In some cases, it does make sense to wrap a new ExecutorService in a try-with-resources statement. That expresses the idea of creating the ES for the express, limited purpose of running the tasks assigned to it in the try clause, waiting for them to complete, and afterward shutting down the ES.

    But a ScheduledExecutorService is a bit different, in that its distinguishing feature is an ability to schedule tasks to be started later. When an SES is closed, all scheduled but unstarted tasks are effectively canceled. They will never be started on account of the SES closure. In your example, control falls out of the try-with-resources statement immediately after your task(s) are scheduled, before any are started, so none ever run. This is try-with-resources working exactly as it should do.

    IntelliJ's diagnostic may be overaggressive, but before you say so, consider that your second example has a flaw: it fails to close / shutdown the SES when it has finished with it. This is exactly the problem that a try-with-resources is intended to solve. Just because ExecutorServices are AutoCloseable does not mean that they should always be used in conjunction with try-with-resources, but try-with-resources nevertheless looks like a good fit in your particular case. To make it work as you seem to intend, you should move the Thread.sleep() into the try clause, preventing the SES from being closed prematurely, but nevertheless ensuring that it is closed when you're ready for that:

    @Test
    public void testScheduledExecutorServiceTryWithResources() throws InterruptedException {
        Logger log = LogManager.getLogger();
    
        try (ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1)) {
            executorService.scheduleWithFixedDelay(() -> log.info("Doing something …"), 0, 1, TimeUnit.SECONDS);
            Thread.sleep(5000);
        } catch(Exception e) {
            log.error("testScheduledExecutorService(): Error occurred: {}", e.getMessage(), e);
        }
    
        log.info("Finishing at {}", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
    }