From e977bd7bf0efe8341a03de025ee6ac376e6a57c8 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 2 Apr 2026 14:30:51 +0200 Subject: [PATCH] Enforce strict implementation of Resource#isReadable() contract Closes gh-36584 --- .../core/io/FileSystemResource.java | 2 +- .../core/io/ResourceTests.java | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java b/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java index ec51f4cefd3..6e3a9b4411d 100644 --- a/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java @@ -179,7 +179,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso */ @Override public boolean isReadable() { - return (this.file != null ? this.file.canRead() && !this.file.isDirectory() : + return (this.file != null ? this.file.exists() && this.file.canRead() && !this.file.isDirectory() : Files.isReadable(this.filePath) && !Files.isDirectory(this.filePath)); } diff --git a/spring-core/src/test/java/org/springframework/core/io/ResourceTests.java b/spring-core/src/test/java/org/springframework/core/io/ResourceTests.java index 9385a210f99..71cb785abcc 100644 --- a/spring-core/src/test/java/org/springframework/core/io/ResourceTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/ResourceTests.java @@ -270,6 +270,50 @@ class ResourceTests { assertThat(relative).isEqualTo(new FileSystemResource("dir/subdir")); } + @Test + void isReadableChecksExistsFirst() { + AtomicBoolean existsCalled = new AtomicBoolean(); + AtomicBoolean canReadCalled = new AtomicBoolean(); + File file = new File("/dev/my.txt") { + @Override + public boolean exists() { + existsCalled.set(true); + return false; + } + @Override + public boolean canRead() { + canReadCalled.set(true); + return false; + } + }; + Resource resource = new FileSystemResource(file); + assertThat(resource.isReadable()).isFalse(); + assertThat(existsCalled).isTrue(); + assertThat(canReadCalled).isFalse(); + } + + @Test + void isReadableChecksCanReadAfterExists() { + AtomicBoolean existsCalled = new AtomicBoolean(); + AtomicBoolean canReadCalled = new AtomicBoolean(); + File file = new File("/dev/my.txt") { + @Override + public boolean exists() { + existsCalled.set(true); + return true; + } + @Override + public boolean canRead() { + canReadCalled.set(true); + return false; + } + }; + Resource resource = new FileSystemResource(file); + assertThat(resource.isReadable()).isFalse(); + assertThat(existsCalled).isTrue(); + assertThat(canReadCalled).isTrue(); + } + @Test void getFilePath() throws Exception { Path path = mock();