File Handling and I/O
Managing Resources with Try-With-Resources
You are viewing a free preview lesson.
Lesson Overview
File, stream, database connection, socket—এগুলো সাধারণ object-এর মতো হলেও একটি extra responsibility আছে:
Use করার পর release বা close করতে হয়
Examples:
InputStream
OutputStream
BufferedReader
BufferedWriter
Resource properly close না করলে:
- File descriptor leak হতে পারে
- File lock unnecessarily ধরে রাখা হতে পারে
- Memory বা native resource leak হতে পারে
- Application eventually new resources open করতে ব্যর্থ হতে পারে
Java এই problem solve করার জন্য provide করে:
try-with-resources
এটি কাজ করে এমন objects-এর সঙ্গে যারা implement করে:
AutoCloseable
এই lesson-এ আমরা শিখব:
- Resource কী
AutoCloseable- Try-with-resources syntax
- Automatic closing
- Multiple resources
- Closing order
- Exception during
close() - Suppressed exceptions
- Resource ownership
- Custom
AutoCloseable finally-based cleanup-এর সমস্যা- Safe resource API design
Learning Objectives
এই lesson শেষে আপনি পারবেন:
- Resource lifecycle explain করতে
AutoCloseableবুঝতে- Try-with-resources লিখতে
- Multiple resources safely manage করতে
- Closing order predict করতে
- Suppressed exception inspect করতে
- Custom closeable resource তৈরি করতে
- Resource ownership define করতে
- Manual cleanup-এর common bugs avoid করতে
What Is a Resource?
A resource এমন কিছু যা application ব্যবহার করে এবং পরে release করা প্রয়োজন।
Examples:
File stream
Reader
Writer
Database connection
Network socket
Directory stream
Lock-backed handle
Resource often refers to something outside ordinary Java heap memory.
Example:
InputStream input =
Files.newInputStream(
path
);
The JVM object exists in memory, but it may also hold:
Operating-system file descriptor
Why Closing Matters
Consider:
InputStream input =
Files.newInputStream(
path
);
process(
input
);
If input is never closed, the underlying file handle may remain open longer than intended.
Repeated many times:
Open file
Forget close
Open file
Forget close
...
eventually the process can run out of file descriptors.
Manual close()
Basic approach:
InputStream input =
Files.newInputStream(
path
);
process(
input
);
input.close();
This looks fine until:
process(
input
);
throws an exception.
Then:
input.close();
never executes.
The Cleanup Problem
InputStream input =
Files.newInputStream(
path
);
process(
input
);
input.close();
Normal flow:
Open
Process
Close
Exceptional flow:
Open
Process throws
Close skipped
This is exactly why resource cleanup needs a stronger mechanism.
Traditional finally Cleanup
Before try-with-resources, common pattern:
InputStream input =
null;
try {
input =
Files.newInputStream(
path
);
process(
input
);
} finally {
if (input != null) {
input.close();
}
}
This is better because finally runs during exceptional flow.
But it still has problems.
Problem: close() Can Also Throw
Suppose:
process() throws IOException A
Then:
input.close() throws IOException B
Which exception should caller see?
Naive finally code may replace the original processing failure with the close failure.
That hides the real cause.
Try-With-Resources
Modern Java provides:
try (
InputStream input =
Files.newInputStream(
path
)
) {
process(
input
);
}
When the block finishes, Java automatically calls:
input.close();
This happens when the block:
- Completes normally
- Returns
- Throws an exception
Basic Example
public static void printFile(
Path path
) {
try (
BufferedReader reader =
Files.newBufferedReader(
path,
StandardCharsets.UTF_8
)
) {
String line;
while (
(
line =
reader.readLine()
)
!= null
) {
System.out.println(
line
);
}
} catch (
IOException exception
) {
throw new IllegalStateException(
"Could not read file.",
exception
);
}
}
No manual:
reader.close();
required.
What Makes an Object Compatible?
A resource must implement:
AutoCloseable
Simplified interface:
public interface AutoCloseable {
void close()
throws Exception;
}
Any compatible object can be declared inside try-with-resources.
Closeable
There is also:
java.io.Closeable
Closeable extends:
AutoCloseable
Many I/O classes implement Closeable.
Examples:
InputStream
OutputStream
Reader
Writer
BufferedReader
BufferedWriter
Automatic Closing
This:
try (
InputStream input =
Files.newInputStream(
path
)
) {
process(
input
);
}
is conceptually similar to:
Open resource
↓
Execute body
↓
Close resource automatically
But Java also handles exception preservation correctly.
Return Still Closes the Resource
public static String firstLine(
Path path
) throws IOException {
try (
BufferedReader reader =
Files.newBufferedReader(
path,
StandardCharsets.UTF_8
)
) {
return reader.readLine();
}
}
Before method actually returns, Java closes:
reader
Exception Still Closes the Resource
try (
BufferedReader reader =
Files.newBufferedReader(
path
)
) {
throw new IllegalStateException(
"Processing failed."
);
}
The reader is still closed.
Then the exception propagates.
Multiple Resources
You can declare multiple resources:
try (
InputStream input =
Files.newInputStream(
source
);
OutputStream output =
Files.newOutputStream(
target
)
) {
input.transferTo(
output
);
}
Both are automatically closed.
Closing Order
Resources close in reverse declaration order.
Declared:
input
output
Actually close:
output
input
This is similar to stack behavior:
Last opened
First closed
Why Reverse Order Makes Sense
Suppose one resource depends on another.
Example:
OutputStream output =
...;
Writer writer =
new OutputStreamWriter(
output,
StandardCharsets.UTF_8
);
You should close:
Writer first
OutputStream second
because writer may need to flush data into the underlying output stream.
Layered Resources
Example:
try (
OutputStream output =
Files.newOutputStream(
path
);
BufferedWriter writer =
new BufferedWriter(
new OutputStreamWriter(
output,
StandardCharsets.UTF_8
)
)
) {
writer.write(
"Java"
);
}
Closing order:
writer
output
Do You Need to Declare Every Wrapper Separately?
Often no.
Simpler:
try (
BufferedWriter writer =
Files.newBufferedWriter(
path,
StandardCharsets.UTF_8
)
) {
writer.write(
"Java"
);
}
Closing BufferedWriter closes its underlying writer and stream.
Prefer the simplest correct API.
Exception During the Try Body
Suppose:
try (
TestResource resource =
new TestResource()
) {
throw new IllegalStateException(
"Processing failed."
);
}
Then:
resource.close()
also throws.
Java needs to preserve both failures.
Primary Exception
The exception thrown from the main try body becomes the primary exception.
Example:
IllegalStateException: Processing failed
The close failure is attached to it as:
suppressed exception
Suppressed Exceptions
Suppose:
Primary:
ProcessingException
Close failure:
IOException
Java keeps:
ProcessingException
as the main failure.
The close failure is available through:
exception.getSuppressed()
Inspecting Suppressed Exceptions
try {
useResource();
} catch (
Exception exception
) {
for (
Throwable suppressed
: exception.getSuppressed()
) {
System.out.println(
"Suppressed: "
+ suppressed.getMessage()
);
}
}
Why Suppressed Exceptions Matter
Without suppression support, cleanup failure could hide the actual operation failure.
Try-with-resources preserves:
What originally failed
+
What also failed during cleanup
This is one of its biggest advantages over naive finally.
Custom AutoCloseable
You can create your own resource.
public final class CourseSession
implements AutoCloseable {
private final String courseCode;
private boolean closed;
public CourseSession(
String courseCode
) {
if (
courseCode == null
|| courseCode.isBlank()
) {
throw new IllegalArgumentException(
"Course code is required."
);
}
this.courseCode =
courseCode.strip();
this.closed =
false;
System.out.println(
"Opened session for "
+ this.courseCode
);
}
public void process() {
if (closed) {
throw new IllegalStateException(
"Session is already closed."
);
}
System.out.println(
"Processing "
+ courseCode
);
}
@Override
public void close() {
if (closed) {
return;
}
closed =
true;
System.out.println(
"Closed session for "
+ courseCode
);
}
}
Using the Custom Resource
try (
CourseSession session =
new CourseSession(
"JAVA-OOP"
)
) {
session.process();
}
Possible output:
Opened session for JAVA-OOP
Processing JAVA-OOP
Closed session for JAVA-OOP
Idempotent close()
Notice:
if (closed) {
return;
}
Calling:
close()
more than once does nothing.
This is often a useful resource design.
A close method should generally avoid damaging state if accidentally invoked repeatedly.
Closed Resource State
After:
close()
the resource should usually reject operations.
Example:
if (closed) {
throw new IllegalStateException(
"Session is already closed."
);
}
This prevents silent use-after-close bugs.
close() Should Be Focused
Avoid:
public void close() {
sendEmail();
callPaymentService();
rebuildCatalog();
}
close() should primarily release resources or finalize resource-specific work.
Complex business operations do not belong there.
Avoid Hiding Important Failures in close()
If cleanup can fail meaningfully:
@Override
public void close()
throws IOException {
}
Try-with-resources can preserve that failure.
Do not silently ignore serious cleanup errors unless the contract explicitly allows it.
Resource Ownership
A fundamental question:
Who is responsible for closing the resource?
A good default:
Who opens it, owns it.
Example:
public void importFile(
Path path
) {
try (
InputStream input =
Files.newInputStream(
path
)
) {
process(
input
);
}
}
importFile() opened the stream, so it closes it.
Caller-Owned Resource
Suppose:
public void process(
InputStream input
) throws IOException {
}
The method did not open the stream.
Usually caller retains ownership.
Caller:
try (
InputStream input =
Files.newInputStream(
path
)
) {
processor.process(
input
);
}
process() should not unexpectedly close it unless documented.
Why Ownership Matters
Suppose:
processor.process(
input
);
readMore(
input
);
If process() secretly closes the stream, readMore() fails.
Clear ownership prevents surprises.
Returning a Resource
A method can deliberately transfer ownership:
public InputStream openContent(
Path path
) throws IOException {
return Files.newInputStream(
path
);
}
Caller:
try (
InputStream input =
storage.openContent(
path
)
) {
process(
input
);
}
Here caller becomes responsible for closing it.
Do Not Close Before Returning
Wrong:
public InputStream openContent(
Path path
) throws IOException {
try (
InputStream input =
Files.newInputStream(
path
)
) {
return input;
}
}
The returned stream is already closed.
Existing Variables in Try-With-Resources
Modern Java can use an effectively final variable:
BufferedReader reader =
Files.newBufferedReader(
path
);
try (
reader
) {
System.out.println(
reader.readLine()
);
}
After the block, reader exists as a variable but the underlying resource is closed.
Do Not Reuse the Closed Resource
BufferedReader reader =
Files.newBufferedReader(
path
);
try (
reader
) {
System.out.println(
reader.readLine()
);
}
reader.readLine();
The final call fails because the reader has been closed.
Variable scope and resource lifecycle are different concepts.
Nested Try-With-Resources
Possible:
try (
InputStream input =
Files.newInputStream(
source
)
) {
try (
OutputStream output =
Files.newOutputStream(
target
)
) {
input.transferTo(
output
);
}
}
But this is usually less readable than:
try (
InputStream input =
Files.newInputStream(
source
);
OutputStream output =
Files.newOutputStream(
target
)
) {
input.transferTo(
output
);
}
Resource Initialization Can Fail
Consider:
try (
InputStream input =
openInput();
OutputStream output =
openOutput()
) {
}
What if:
input
opens successfully but:
output
creation throws?
Java automatically closes already-created:
input
before propagating the initialization failure.
This is another major safety benefit.
Closing Order Example
public final class DemoResource
implements AutoCloseable {
private final String name;
public DemoResource(
String name
) {
this.name = name;
System.out.println(
"Open "
+ name
);
}
@Override
public void close() {
System.out.println(
"Close "
+ name
);
}
}
Usage:
try (
DemoResource first =
new DemoResource(
"A"
);
DemoResource second =
new DemoResource(
"B"
);
DemoResource third =
new DemoResource(
"C"
)
) {
System.out.println(
"Use resources"
);
}
Output:
Open A
Open B
Open C
Use resources
Close C
Close B
Close A
Manual finally Is More Error-Prone
Manual:
InputStream input =
null;
try {
input =
Files.newInputStream(
path
);
process(
input
);
} finally {
if (input != null) {
input.close();
}
}
Problems:
- More code
- Null handling
- Close can throw
- Original exception may be obscured
- Multiple resources become complicated
Try-with-resources handles all of these better.
Multiple Resources with Manual Cleanup
Imagine:
InputStream input;
OutputStream output;
BufferedWriter writer;
You must close in reverse order and preserve failures correctly.
This becomes tedious and fragile.
Try-with-resources expresses lifecycle directly in syntax.
finally Still Has Uses
Try-with-resources is specifically for closeable resources.
finally can still be useful for non-resource cleanup:
Release a lock
Restore temporary state
Clear thread-local context
Stop timing measurement
Example:
lock.lock();
try {
update();
} finally {
lock.unlock();
}
A lock is not necessarily modeled with AutoCloseable.
Custom Lock Resource
Sometimes you can create an abstraction:
public final class LockHandle
implements AutoCloseable {
private final Lock lock;
public LockHandle(
Lock lock
) {
this.lock = lock;
this.lock.lock();
}
@Override
public void close() {
lock.unlock();
}
}
Then:
try (
LockHandle ignored =
new LockHandle(
lock
)
) {
update();
}
This can make ownership explicit, but only introduce such abstractions when they genuinely improve design.
Exceptions from close()
A custom AutoCloseable can declare:
@Override
public void close()
throws Exception {
}
But broad Exception can make APIs awkward.
When possible, use a more specific exception type.
Example:
@Override
public void close()
throws IOException {
}
AutoCloseable vs Closeable
Simplified distinction:
AutoCloseable.close()
may throw Exception
Closeable.close()
throws IOException
Closeable is specialized for many I/O resources.
For custom general-purpose resources, AutoCloseable is often sufficient.
A Resource Should Not Be Shared Carelessly
Suppose two components share one writer:
Service A
Service B
↓
Same BufferedWriter
If Service A closes it, Service B breaks.
Resource sharing requires explicit lifecycle ownership.
Prefer:
One clear owner
over uncertain shared ownership.
Long-Lived Resources
Not every resource should be opened and closed per method call.
Examples:
Connection pool
Application-level HTTP client
Thread pool
These may live for the application lifetime and close during application shutdown.
The same ownership principle still applies:
The component that owns lifecycle is responsible for closing it.
Resource Factory Pattern
A component may create short-lived resources:
public InputStream open(
CourseCode courseCode
)
Caller owns each returned stream.
Alternatively, hide resource management:
public String read(
CourseCode courseCode
)
The storage component opens, reads, closes, then returns data.
The second design is often simpler for callers.
Prefer APIs That Hide Resource Management When Possible
Compare:
InputStream input =
storage.open(
courseCode
);
Caller must remember to close it.
Versus:
String content =
storage.read(
courseCode
);
Storage handles lifecycle internally.
Use open-resource APIs only when streaming is genuinely needed.
Streaming APIs Need Clear Ownership
For large files:
InputStream openContent(
CourseCode courseCode
)
may be appropriate.
Document:
Caller must close the returned stream.
This is part of the API contract.
Do Not Hide Close Failures by Returning from finally
Bad:
try {
process();
} finally {
return;
}
This can suppress:
- Processing exception
- Close exception
- Actual failure signal
Avoid control flow from finally.
Complete Example: Course Content Import
package io.liveklass.content;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public final class CourseContentImporter {
public int countNonBlankLines(
Path path
) {
if (path == null) {
throw new IllegalArgumentException(
"Content path is required."
);
}
try (
BufferedReader reader =
Files.newBufferedReader(
path,
StandardCharsets.UTF_8
)
) {
int count =
0;
String line;
while (
(
line =
reader.readLine()
)
!= null
) {
if (!line.isBlank()) {
count++;
}
}
return count;
} catch (
IOException exception
) {
throw new CourseContentImportException(
"Could not import course content.",
exception
);
}
}
}
Custom Import Exception
package io.liveklass.content;
public final class CourseContentImportException
extends RuntimeException {
public CourseContentImportException(
String message,
Throwable cause
) {
super(
message,
cause
);
}
}
Design Review
Who Opens the Reader?
countNonBlankLines()
does.
Therefore it owns and closes the reader.
Why Try-With-Resources?
Reader closes on:
- Success
- Return
- Parsing failure
- I/O failure
Why Translate IOException?
Higher layers see course-import semantics instead of raw storage implementation details.
Common Mistakes
Forgetting to Close a Resource
Can leak operating-system resources.
Closing Only on the Happy Path
Exceptions can skip manual close() calls.
Returning a Resource from Inside Its Try-With-Resources Block
The caller receives a closed resource.
Closing Caller-Owned Resources
Breaks ownership expectations.
Sharing One Resource Without Clear Ownership
One component may close it while another still needs it.
Using Manual finally When Try-With-Resources Fits
Adds avoidable complexity.
Forgetting Reverse Close Order
Dependent resources should close from outermost to underlying resource.
Ignoring Suppressed Exceptions
Can hide useful cleanup diagnostics.
Throwing from finally
May replace the original failure.
Returning from finally
May suppress exceptions entirely.
Making close() Perform Business Operations
Resource cleanup should remain focused.
Practice Exercises
Exercise 1: Read a File Safely
Create:
String readFirstLine(
Path path
)
Use:
BufferedReader
try-with-resources
Translate IOException while preserving the cause.
Exercise 2: Copy Two Streams
Open:
InputStream
OutputStream
inside one try-with-resources block.
Copy using:
transferTo()
Explain the close order.
Exercise 3: Custom Resource
Create:
ProcessingSession
implements AutoCloseable
Requirements:
- Print when opened
- Reject work after close
close()may be called more than once safely
Exercise 4: Predict Closing Order
Resources declared:
A
B
C
D
What is the closing order?
Exercise 5: Ownership
Method:
void process(
InputStream input
)
receives a caller-created stream.
Should it close it automatically?
Explain the default ownership rule.
Exercise 6: Suppressed Failure
Create a custom resource whose:
close()
throws an exception.
Inside the try body, throw another exception.
Inspect:
getSuppressed()
Exercise 7: Fix the Bug
public InputStream open(
Path path
) throws IOException {
try (
InputStream input =
Files.newInputStream(
path
)
) {
return input;
}
}
Explain what is wrong and correct the method.
Predict the Result
Question 1
try (
DemoResource a =
new DemoResource(
"A"
);
DemoResource b =
new DemoResource(
"B"
)
) {
System.out.println(
"Work"
);
}
What is the close order?
Answer
B
A
Question 2
A resource is opened inside try-with-resources and the method returns from inside the block.
Is the resource closed?
Answer
Yes.
It is closed before the method completes the return.
Question 3
The try body throws exception A and close() throws exception B.
Which one is primary?
Answer
Exception A.
Exception B becomes a suppressed exception.
Question 4
Can any arbitrary Java object be used in try-with-resources?
Answer
No.
It must implement:
AutoCloseable
or a subtype such as:
Closeable
Question 5
A method opens an InputStream, fully processes it, and does not return it.
Who should normally close it?
Answer
The method itself, because it owns the resource lifecycle.
Knowledge Check
Question 1
What is a resource?
Question 2
Why must resources be closed?
Question 3
What is AutoCloseable?
Question 4
What does try-with-resources guarantee?
Question 5
In what order are multiple resources closed?
Question 6
What happens when the try body throws and close() also throws?
Question 7
What is a suppressed exception?
Question 8
How can suppressed exceptions be inspected?
Question 9
Why is try-with-resources safer than naive finally cleanup?
Question 10
What is resource ownership?
Question 11
Who should normally close a resource?
Question 12
Why should a method not return a resource created inside its own try-with-resources block?
Question 13
Can a custom class participate in try-with-resources?
Question 14
Why should close() usually be focused and idempotent?
Question 15
When might finally still be appropriate?
Knowledge Check Answers
Answer 1
An object that owns an external or limited resource requiring explicit release after use.
Answer 2
To release file descriptors, buffers, connections, locks, or other underlying resources promptly.
Answer 3
An interface with a close() contract that makes objects compatible with try-with-resources.
Answer 4
Declared resources are automatically closed when the block finishes normally or exceptionally.
Answer 5
Reverse declaration order.
Answer 6
The body exception remains primary and the close exception is attached as suppressed.
Answer 7
A secondary failure preserved while another exception remains the primary failure.
Answer 8
Using:
exception.getSuppressed()
Answer 9
It handles closing, reverse order, initialization failures, and suppressed exceptions automatically.
Answer 10
The contract defining which component is responsible for closing a resource.
Answer 11
Usually the component that opens and owns it.
Answer 12
The resource is closed before the caller receives it.
Answer 13
Yes, by implementing AutoCloseable.
Answer 14
Focused cleanup keeps lifecycle predictable; idempotent close avoids damage from repeated close calls.
Answer 15
For cleanup that is not represented by an AutoCloseable resource, such as unlocking or restoring temporary state.
Lesson Summary
এই lesson-এ আমরা শিখেছি:
- Resources often wrap external or limited system resources
- Resources should be released promptly
- Manual happy-path
close()is unsafe when exceptions occur AutoCloseableenables try-with-resourcesCloseableis an I/O-focused subtype ofAutoCloseable- Try-with-resources closes resources automatically
- Resources close on success, return, or exception
- Multiple resources close in reverse declaration order
- Already-created resources are closed if later resource initialization fails
- Try-body exceptions remain primary
- Cleanup failures become suppressed exceptions
getSuppressed()exposes suppressed failures- Custom classes can implement
AutoCloseable - Resource ownership must be explicit
- The component that opens a resource usually closes it
- Caller-owned resources should not be closed unexpectedly
- Returning a resource from inside its own try-with-resources block returns a closed resource
close()should remain focused and preferably safe to call repeatedly- Manual
finallycleanup is more error-prone finallyis still useful for non-resource cleanup- High-level APIs that hide resource management are preferable when streaming is unnecessary
- Streaming APIs must clearly document caller ownership
Next Lesson
পরবর্তী lesson:
File and Directory Operations
আমরা শিখব:
- Creating files and directories
- Copying files
- Moving and renaming
- Deleting files
delete()vsdeleteIfExists()- Directory listing
- Recursive traversal
Files.walk()- File replacement options
- Safe cleanup
- Common file-system race conditions