How to Load and Save Word Documents with Streams and Async in C#
IronWord 2026.10.1+ introduces direct loading from and saving to any System.IO.Stream, together with async methods for both. Loading and saving previously worked through file paths and byte arrays. A .docx that arrives as a web upload, comes down from blob storage, or goes back out in an HTTP response never has to touch the disk, so serverless functions and web endpoints stay fully in memory.
Three entry points into WordDocument cover every scenario: a file path when the .docx is on disk, a stream when the bytes are already flowing, and a byte array when everything is already in memory.
Pass any readable stream to the WordDocument constructor, then save into another stream, as in the load and save with streams example.
-
1Install IronWord with NuGet Package Manager
-
2Copy and run this code snippet.
using var input = File.OpenRead("source.docx"); var doc = new WordDocument(input); using var output = new MemoryStream(); doc.Save(output);C# -
3Deploy to test on your live environment
Start using IronWord in your project today with a free trial
Minimal Workflow (5 steps)
Which Load and Save Overload Should I Use?
Pick the overload that matches where the data is.
| Method | Direction | I/O | Async |
|---|---|---|---|
WordDocument(string filePath) | Load | File | No |
WordDocument(byte[] bytes) | Load | None | No |
WordDocument(Stream stream) | Load | Stream | No |
WordDocument.LoadAsync(string, CancellationToken) | Load | File | Yes |
WordDocument.LoadAsync(Stream, CancellationToken) | Load | Stream | Yes |
Save() returns byte[] | Save | None | No |
Save(string filePath) | Save | File | No |
Save(Stream stream) | Save | Stream | No |
SaveAs(string filePath) | Save | File | No |
SaveAsync(string, CancellationToken) | Save | File | Yes |
SaveAsync(Stream, CancellationToken) | Save | Stream | Yes |
SaveAsAsync(string, CancellationToken) | Save | File | Yes |
Byte arrays stay synchronous by design: constructing from bytes and serializing to a byte array perform no I/O, so there is nothing to await. To produce a document entirely in memory and get it as bytes, call byte[] Save().
Saving requires a valid IronWord license, the same as every other save path.
What Is the Stream Contract?
Five rules govern how IronWord treats a caller's stream.
- Stream ownership: IronWord never closes a caller's stream, so the caller owns and disposes it
- Loading: reads from the current position to the end and leaves the stream at its end
- Saving: writes at the current position
- No truncation: a reused stream that already held a longer document keeps its old trailing bytes after the new content and the result is a corrupt .docx, so reset it with
SetLength(0)before saving - Non-seekable streams: supported as both source and target, since neither load nor save seeks
using IronWord;
using IronWord.Models;
// Build a document entirely in memory
var doc = new WordDocument();
doc.AddParagraph(new Paragraph(new TextContent("Invoice #1042")));
doc.AddParagraph(new Paragraph(new TextContent("Total: $2,450.00")));
// Save to a MemoryStream
using var ms = new MemoryStream();
doc.Save(ms);
// Reset the position before handing the stream to another consumer
ms.Position = 0;
// Print the document size
Console.WriteLine($"Document size: {ms.Length} bytes");
How Do I Use Streams in ASP.NET Core?
ASP.NET Core: new WordDocument(Request.Body) and doc.Save(Response.Body) throw under Kestrel's default, so use LoadAsync and SaveAsync
What Does the Uploaded Document Look Like?
The upload is a short vendor memo with a title and two paragraphs.

using IronWord;
using IronWord.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
var app = WebApplication.Create();
// Upload a .docx, append a confidentiality notice, and return the modified document
app.MapPost("/add-notice", async (HttpRequest request) =>
{
// Load the request body asynchronously
var doc = await WordDocument.LoadAsync(request.Body);
// Append a confidentiality notice
doc.AddParagraph(new Paragraph(new TextContent("CONFIDENTIAL")));
// Set the download headers
var response = request.HttpContext.Response;
response.ContentType =
"application/vnd.openxmlformats-officedocument.wordprocessingml.document";
response.Headers.ContentDisposition = "attachment; filename=modified.docx";
// Save to the response body asynchronously
await doc.SaveAsync(response.Body);
return Results.Empty;
});
app.Run();
What Does the Endpoint Return?
The endpoint returns the same memo with a CONFIDENTIAL paragraph appended.

What Do the Async Methods Actually Do?
Only the file or stream I/O is asynchronous and cancellable. Parsing a loaded document and serializing a saved one are synchronous, CPU-bound steps that the CancellationToken cannot interrupt. The token is checked before that work and around the I/O calls.
What Does the Report Look Like Before the Save?
report.docx holds a title and two paragraphs.

using IronWord;
using IronWord.Models;
// Load a document from disk asynchronously
var doc = await WordDocument.LoadAsync("report.docx");
// Append a dated paragraph
doc.AddParagraph(new Paragraph(
new TextContent("Appendix added on " + DateTime.Now.ToString("yyyy-MM-dd"))));
// Save to a new path, leaving the original untouched
await doc.SaveAsAsync("report-updated.docx");
What Does the Updated Report Look Like?
report-updated.docx gains the dated appendix line, and report.docx stays unchanged.

Is Cancellation Safe for File Saves?
Cancellation is safe only if the token is already cancelled when the call begins. In that case, a path save throws before the destination file is opened, so an existing file is untouched.
After writing starts: a cancelled save can leave the file empty or partly written, with its old contents gone, and a stream partly written
Is WordDocument Thread-Safe?
No. A single WordDocument instance is not thread-safe. Do not run save operations concurrently against the same instance: Task.WhenAll(doc.SaveAsync(pathA), doc.SaveAsync(pathB)) produces undefined behavior.
Frequently Asked Questions
How do I load a Word document from a stream in C# using IronWord?
Pass any readable stream, such as a MemoryStream or an uploaded file, to the WordDocument constructor. IronWord reads the document from the stream's current position to the end.
How can I save a Word document to a stream with IronWord?
Call Save with any writable stream, such as a MemoryStream, to write the document without saving it to disk. IronWord writes the document at the stream's current position.
Can I load and save Word documents asynchronously in C#?
Yes. IronWord provides LoadAsync for file paths and streams, plus SaveAsync and SaveAsAsync for saving, so async application code does not block a thread on file I/O.
Does IronWord close the stream after loading or saving a document?
No. IronWord never closes a caller's stream. The application that opened the stream owns it and is responsible for disposing it.
Why is my saved Word document corrupt when I reuse a stream?
Saving into a stream that already holds a longer document leaves its old trailing bytes in place, which corrupts the output. Reset or truncate the stream before saving into it again.
Can a CancellationToken stop IronWord while it loads or saves a document?
The token cancels the file or stream I/O only. Parsing a loaded document and serializing a saved one are synchronous steps that the token cannot interrupt.
Is there an async method for loading or saving byte arrays in IronWord?
No, by design. Loading from a byte array and saving to one involve no I/O, so IronWord keeps the synchronous byte array constructor and Save method for those in-memory cases.
Is a WordDocument thread-safe in IronWord?
No. A single WordDocument instance is not thread-safe, so do not run save operations concurrently against the same document, for example with Task.WhenAll.
Can I load a Word document from an upload without saving it to disk first?
Yes. Pass the upload's stream straight to the WordDocument constructor or LoadAsync, edit the document in memory, and save it back to a stream, so the file never touches the disk.
Does saving a Word document to a stream require an IronWord license?
Yes. Saving to a stream requires a valid IronWord license, the same as saving to a file path or a byte array.

Ahmad is a full-stack developer with a strong foundation in C#, Python, and web technologies. He has a deep interest in building scalable software solutions and enjoys exploring how design and functionality meet in real-world applications.