eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

1. Overview

In this tutorial, we’ll look at the performance differences between two basic Java classes for writing files: FileWriter and BufferedWriter. While conventional wisdom on the web often suggests that BufferedWriter typically outperforms FileWriter, our goal is to put this assumption to the test.

After looking at the basic information about using classes, their inheritance, and their internal implementation, we’ll use the Java Microbenchmark Harness (JMH) to test whether BufferedWriter really has an advantage.

We’ll run the tests with JDK17 on Linux, but we can expect similar results with any recent version of the JDK on any operating system.

2. Basic Usage

FileWriter writes text to character files using a default buffer whose size isn’t specified in the Javadoc:

FileWriter writer = new FileWriter("testFile.txt");
writer.write("Hello, Baeldung!");
writer.close();

BufferedWriter is an alternative choice. It’s designed to wrap around other Writer classes, including FileWriter:

int BUFSIZE = 4194304; // 4MiB
BufferedWriter writer = new BufferedWriter(new FileWriter("testBufferedFile.txt"), BUFSIZE);
writer.write("Hello, Buffered Baeldung!");
writer.close();

In this case, we specified a 4MiB buffer. However, if we don’t set the size for the buffer, its default size isn’t specified in the Javadoc.

3. Inheritance

Here is a UML diagram illustrating the inheritance structure of FileWriter and BufferedWriter:

FileWrite and BufferedWriter Hierarchy

It’s helpful to understand that FileWriter and BufferedWriter both extend Writer, and the operation of FileWriter is based on OutputStreamWriter. Unfortunately, neither the analysis of inheritance hierarchies nor the Javadocs tell us enough about the default buffer size of FileWriter and BufferedWriter, so we’ll inspect the JDK source code to understand more.

4. Underlying Implementation

Looking at the underlying implementation of FileWriter, we see that its default buffer size is 8192 bytes from JDK10 to JDK18, and variable from 512 to 8192 in later versions. Specifically, FileWriter extends OutputStreamWriter, as we’ve just seen in the UML diagram, and OutputStreamWriter uses StreamEncoder, whose code contains DEFAULT_BYTE_BUFFER_SIZE = 8192 up to JDK18 and MAX_BYTE_BUFFER_CAPACITY = 8192 in later versions.

StreamEncoder isn’t a public class in the JDK API. It’s an internal class in the sun.nio.cs package that is used within the Java framework to handle encoding of character streams.

Its buffer size allows FileWriter to handle data efficiently by minimizing the number of I/O operations. Since the default character encoding in Java is typically UTF-8, 8192 bytes would correspond to approximately 8192 characters in most scenarios. Despite this efficient buffering, FileWriter is still considered to have no buffering capabilities due to outdated documentation.

The default buffer size of BufferedWriter is the same as FileWriter. We can verify it by checking its source code, which contains defaultCharBufferSize = 8192 from JDK10 to JDK18, and DEFAULT_MAX_BUFFER_SIZE = 8192 in later versions. However, BufferedWriter allows us to specify a different buffer size, as we’ve seen in the previous example.

5. Comparing Performance

Here we’ll compare FileWriter and BufferedWriter with JMH. If we want to replicate the tests on our machine, and if we’re using Maven, we’ll need to set up the JMH dependencies on pom.xml, add the JMH annotation processor to the Maven compiler plugin configuration, and make sure all the required classes and resources are available during execution. The Getting Started section of our JMH tutorial covers these points.

5.1. Disk Write Synchronization

To perform disk write benchmarking with JHM, it’s imperative to achieve full synchronization of disk operations by disabling the operating system cache. This step is critical because asynchronous disk writes can significantly affect the accuracy of I/O operations measurements. By default, operating systems store frequently accessed data in memory, reducing the number of actual disk writes and invalidating benchmark results.

On Linux systems, we can remount the filesystem with the sync option of mount to disable caching and ensure that all write operations are immediately synchronized to disk:

$ sudo mount -o remount,sync /path/to/mount

Similarly, the macOS mount has a sync option that ensures that all I/O to the filesystem is synchronous.

On Windows, we open the Device Manager and expand the Drives section. Then we right-click on the drive we want to configure, select Properties, and navigate to the Policies tab. Finally, we disable the Enable write caching on the device option.

5.2. Our Tests

Our code measures the performance of FileWriter and BufferedWriter under various write conditions. We run several benchmarks to test single writes and repeated writes (10, 1000, 10000, and 100000 times) to the benchmark.txt file.

We use JMH-specific annotations to configure the benchmark parameters such as @Benchmark, @State, @BenchmarkMode, and others to set the scope, mode, warm-up iterations, measurement iterations, and fork settings.

The main method sets up the environment by deleting any existing benchmark.txt file and adjusting the classpath before running the JMH benchmarking suite:

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10, time = 10, timeUnit = TimeUnit.SECONDS)
@Fork(1)
public class BenchmarkWriters {
    private static final Logger log = LoggerFactory.getLogger(BenchmarkWriters.class);
    private static final String FILE_PATH = "benchmark.txt";
    private static final String CONTENT = "This is a test line.";
    private static final int BUFSIZE = 4194304; // 4MiB

    @Benchmark
    public void fileWriter1Write() {
        try (FileWriter writer = new FileWriter(FILE_PATH, true)) {
            writer.write(CONTENT);
            writer.close();
        } catch (IOException e) {
            log.error("Error in FileWriter 1 write", e);
        }
    }

    @Benchmark
    public void bufferedWriter1Write() {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(FILE_PATH, true), BUFSIZE)) {
            writer.write(CONTENT);
            writer.close();
        } catch (IOException e) {
            log.error("Error in BufferedWriter 1 write", e);
        }
    }

    @Benchmark
    public void fileWriter10Writes() {
        try (FileWriter writer = new FileWriter(FILE_PATH, true)) {
            for (int i = 0; i < 10; i++) {
                writer.write(CONTENT);
            }
            writer.close();
        } catch (IOException e) {
            log.error("Error in FileWriter 10 writes", e);
        }
    }

    @Benchmark
    public void bufferedWriter10Writes() {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(FILE_PATH, true), BUFSIZE)) {
            for (int i = 0; i < 10; i++) {
                writer.write(CONTENT);
            }
            writer.close();
        } catch (IOException e) {
            log.error("Error in BufferedWriter 10 writes", e);
        }
    }

    @Benchmark
    public void fileWriter1000Writes() {
        [...]
    }

    @Benchmark
    public void bufferedWriter1000Writes() {
        [...]
    }
    
    @Benchmark
    public void fileWriter10000Writes() {
        [...]
    }

    @Benchmark
    public void bufferedWriter10000Writes() {
        [...]
    }
    
    @Benchmark
    public void fileWriter100000Writes() {
        [...]
    }

    @Benchmark
    public void bufferedWriter100000Writes() {
        [...]
    }

    [...]
}

In these tests, each benchmark method opens and closes the file writer independently. The @Fork(1) annotation indicates that only one fork is used, so there are no multiple parallel executions of the same benchmark method. The code doesn’t explicitly create or manage threads, so all writes are done in the main thread of the benchmark.

All this means that the writes are indeed sequential and not concurrent, which is necessary to get valid measurements.

5.3. Results

These are the results with the BufferedWriter buffer size of 4MiB specified in the code:

Benchmark                                    Mode  Cnt     Score     Error  Units
BenchmarkWriters.bufferedWriter100000Writes  avgt   10  9170.583 ± 245.916  ms/op
BenchmarkWriters.bufferedWriter10000Writes   avgt   10   918.662 ±  15.105  ms/op
BenchmarkWriters.bufferedWriter1000Writes    avgt   10   114.261 ±   2.966  ms/op
BenchmarkWriters.bufferedWriter10Writes      avgt   10    37.999 ±   1.571  ms/op
BenchmarkWriters.bufferedWriter1Write        avgt   10    37.968 ±   2.219  ms/op
BenchmarkWriters.fileWriter100000Writes      avgt   10  9253.935 ± 261.032  ms/op
BenchmarkWriters.fileWriter10000Writes       avgt   10   951.684 ±  41.391  ms/op
BenchmarkWriters.fileWriter1000Writes        avgt   10   114.610 ±   4.366  ms/op
BenchmarkWriters.fileWriter10Writes          avgt   10    37.761 ±   1.836  ms/op
BenchmarkWriters.fileWriter1Write            avgt   10    37.912 ±   2.080  ms/op

Instead, these are the results without specifying a buffer value for BufferedWriter, i.e., using its default buffer:

Benchmark                                    Mode  Cnt     Score     Error  Units
BenchmarkWriters.bufferedWriter100000Writes  avgt   10  9117.021 ± 143.096  ms/op
BenchmarkWriters.bufferedWriter10000Writes   avgt   10   931.994 ±  34.986  ms/op
BenchmarkWriters.bufferedWriter1000Writes    avgt   10   113.186 ±   2.076  ms/op
BenchmarkWriters.bufferedWriter10Writes      avgt   10    40.038 ±   2.042  ms/op
BenchmarkWriters.bufferedWriter1Write        avgt   10    38.891 ±   0.684  ms/op
BenchmarkWriters.fileWriter100000Writes      avgt   10  9261.613 ± 305.692  ms/op
BenchmarkWriters.fileWriter10000Writes       avgt   10   932.001 ±  26.676  ms/op
BenchmarkWriters.fileWriter1000Writes        avgt   10   114.209 ±   5.988  ms/op
BenchmarkWriters.fileWriter10Writes          avgt   10    38.205 ±   1.361  ms/op
BenchmarkWriters.fileWriter1Write            avgt   10    37.490 ±   2.137  ms/op

In essence, these results show that the performance of FileWriter and BufferedWriter is nearly identical under all test conditions. Furthermore, specifying a larger buffer for BufferedWriter than the default one doesn’t provide any benefit.

6. Conclusion

In this article, we explored the performance differences between FileWriter and BufferedWriter using JHM. We began by looking at their basic usage and inheritance structures. Both classes have a default buffer size of 8192 bytes from JDK10 to JDK18, and variable from 512 to 8192 bytes in later versions.

We ran benchmarks to compare their performance under various conditions, ensuring accurate measurements by disabling operating system caching. The tests included single and repetitive writes using both the default and a specified 4MiB buffer for BufferedWriter.

Our results show that FileWriter and BufferedWriter have nearly identical performance in all scenarios. Furthermore, increasing the buffer size of BufferedWriter doesn’t significantly improve performance.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)