Course outline

Java / Intermediate / Lesson 10

I/O, paths, charsets, and time

Build explicit boundaries between bytes, text, files, and temporal values. Reason about bounded input, resource ownership, and filesystem assumptions.

About 5 min readContent reviewed
On this page

Lesson goals

  • Decode bytes with an explicit charset.
  • Distinguish lexical paths from real filesystem identity.
  • Choose temporal types and bounded I/O policies.

Define the encoding boundary

Files and network messages contain bytes; String represents text as UTF-16 units. A charset maps between the representations. Specify StandardCharsets.UTF_8 when the format requires UTF-8 rather than inheriting a machine default. The default charset changed across JDK history, so a Java 17 application should not silently rely on the environment to interpret a portable file format.

The example converts an Arabic greeting to UTF-8 bytes and back. Equality is true because both sides use the same encoding and no bytes are lost. Do not generalize this valid round trip to malformed input. Decoder error policy matters: some convenience operations replace malformed sequences, while a configured CharsetDecoder can report errors for strict import validation.

java An explicit UTF-8 round trip
public class Main {

 public static void main(String[] args) throws Exception {
String text="مرحبا";
byte[] bytes=text.getBytes(java.nio.charset.StandardCharsets.UTF_8);
String decoded=new String(bytes,java.nio.charset.StandardCharsets.UTF_8);
System.out.println(text.equals(decoded));
System.out.println(decoded);
 }
}
Expected output
true
مرحبا

Both conversions agree on UTF-8, preserving the synthetic Arabic greeting. Compilation also uses UTF-8 source encoding.

Bound input before allocating everything

A convenience method that reads an entire file into a byte array or string is suitable only when the size is bounded by contract. For large or untrusted input, stream data in manageable chunks and enforce a maximum, including decompressed size when archives are involved. An available-memory check is not a reliable substitute for a deliberate limit under concurrency.

Use try-with-resources for resources the operation owns. A Files.lines stream itself needs closing because it holds a file resource, even if a terminal stream operation completes. A StringReader or ByteArrayInputStream in a lesson avoids real files but still helps explain lifecycle. Define partial-read failure behavior and whether a consumer may receive an incomplete result.

Keep lexical normalization separate from access checks

Path.resolve combines path components, and normalize simplifies lexical dot segments. It does not access the filesystem or resolve symbolic links. A path that looks contained under a base directory may still reach elsewhere through a link, platform-specific behavior, or a race between checking and opening. Treat file access as a security boundary rather than a string-cleaning exercise.

The example prints only the final filename after lexical normalization, making its output independent of operating-system separator style. It performs no file access. Real upload or export code should choose a trusted root, validate caller-selected names, apply appropriate real-path and no-follow policies, and account for race conditions through an approved filesystem design. Do not promise that normalize alone prevents path traversal.

java Lexical path operations without filesystem access
public class Main {

 public static void main(String[] args) throws Exception {
var path=java.nio.file.Path.of("training").resolve("reports/../summary.txt").normalize();
System.out.println(path.getFileName());
 }
}
Expected output
summary.txt

The parent segment cancels reports lexically; getFileName returns summary.txt. No existence or security claim follows.

Represent time with the right semantics

Instant represents a point on the timeline, LocalDate a calendar date without time or zone, and ZonedDateTime a date-time with zone rules. LocalDateTime alone is not a globally unique instant. A daylight-saving transition can create missing or repeated local times, so conversions require an explicit policy. Store a suitable stable representation and format for users at the boundary.

Inject a Clock into time-dependent business logic instead of repeatedly calling the system clock throughout a calculation. This makes expiry and scheduling tests deterministic. Use Duration for elapsed time and Period for calendar-based differences where that matches the domain. For measuring elapsed execution time, System.nanoTime is a monotonic-duration tool, not a wall-clock timestamp to persist as an event date.

Verify formats and environment assumptions

An I/O contract should state charset, maximum size, record delimiters, malformed-input policy, time convention, and resource ownership. Test empty input, an incomplete final line, non-ASCII text, invalid byte sequences, oversized content, and close behavior. A sample that only reads a short ASCII file cannot establish multilingual correctness or safe memory use.

Separate parsing from file acquisition so most tests can use in-memory bytes or readers. Keep filesystem integration tests in temporary directories with cleanup, avoiding real user files and external services. Report errors with enough restricted context to distinguish permission, missing resource, malformed data, and cancellation, while keeping sensitive paths or file contents out of public messages.

Common errors and fixes

MalformedInputException

Why it happens
Bytes do not satisfy the configured decoding contract.
How to fix it
Reject or quarantine the input and verify the producer's charset.
Prevent it next time
Use explicit charsets and malformed-input tests.

OutOfMemoryError while reading a file

Why it happens
Unbounded content was materialized or too many reads ran concurrently.
How to fix it
Enforce size and concurrency limits and stream appropriate input.
Prevent it next time
Test oversized and compressed input budgets.

Path normalization was treated as authorization

Why it happens
Lexical cleanup ignored filesystem links and races.
How to fix it
Use a trusted-root access design and appropriate filesystem checks.
Prevent it next time
Review file access as a security boundary.

Check your understanding

1. A file is UTF-8 but one machine reads it with another default charset. What changes?

Show worked solution

The same bytes can decode to different or invalid text. Specify UTF-8 in the reader and validate malformed data. Do not repair the displayed string afterward when the original decoding boundary is wrong.

2. An order expires 24 elapsed hours after creation. Which temporal tools help testing?

Show worked solution

Use an Instant plus Duration.ofHours(24) and inject a fixed Clock for tests. Compare instants at the boundary, including exactly expired. A calendar day in a local zone can differ from 24 hours across daylight-saving changes.

Key takeaways

  • Bytes need a charset; time values need a semantic type.
  • Normalize is lexical and does not authorize file access.
  • Bound input size, concurrency, and resource lifetime.

Official references