Merging PDFs sounds simple — until you try to do it in a real-world .NET application running on a server.
Without a local desktop environment, approaches that rely on Microsoft Office quickly fall apart: licensing restrictions, deployment headaches, and poor performance under load make it a poor fit for server-side .NET applications.
That's usually the point where developers start looking for alternatives.
The good news is you don't need Office at all. Several dedicated .NET libraries handle PDF merging cleanly, without any Office dependency — and they give you far more control over the output.
In this guide, we'll walk through the practical side of merging PDFs in .NET: a working setup, the issues developers most commonly run into, and how to fix them.
A Clean Way to Merge PDFs in .NET
When merging PDFs in a .NET application—especially on the server—the biggest challenge isn't writing the code itself. It's making sure the solution is reliable, scalable, and doesn't depend on external software like Microsoft Office.
A more robust approach is to use a dedicated .NET PDF library that works independently of Office. This avoids common deployment issues and makes your solution suitable for environments like ASP.NET Core, Docker containers, or cloud services.
In this article, we'll use Spire.PDF for .NET as an example to demonstrate the process. The main reasons are:
- It works completely without Microsoft Office
- The API is straightforward and easy to integrate
- It handles common PDF merging scenarios (including large files and complex documents)
That said, the approach shown here isn't limited to a single library. The same concepts apply to most modern .NET PDF libraries—you just need one that is stable, server-friendly, and doesn't introduce licensing or deployment complications.
In the next section, we'll walk through the simplest way to merge PDFs in C# with a minimal, working example.
Getting Started with Spire.PDF
Install the library
Install Spire.PDF via NuGet with a single command:
dotnet add package Spire.PDF
Or search for Spire.PDF in the Visual Studio NuGet Package Manager.
Basic PDF Merging - C# Sample
Here's the simplest way to merge PDFs in C#:
using Spire.Pdf;
namespace MergePDFs
{
class Program
{
static void Main(string[] args)
{
// Specify the PDF files to be merged
string[] files = new string[] {"sample0.pdf", "sample1.pdf", "sample2.pdf"};
// Merge PDF files
PdfDocumentBase pdf = PdfDocument.MergeFiles(files);
// Save the result file
pdf.Save("MergePDF.pdf", FileFormat.PDF);
}
}
}
What's happening here:
-
PdfDocument.MergeFiles()method to merge multiple PDFs into a single file. -
pdf.Savewrites the merged document to disk in PDF format.
Common Issues & Solutions
Even with a straightforward API, PDF merging can go wrong in a few predictable ways. This section covers the most common ones — starting with the issues that tend to cause the most confusion.
Issue 1: Corrupted Output File
The merged file is generated, but when you try to open it, your PDF reader throws an error — or the file size looks suspiciously small.
Why does this happen
The most common cause is that SaveToFile() or Close() was never called — or was called in the wrong order. If the program exits or throws an exception before the file is properly finalized, the output will be incomplete.
How to fix it
Always release resources after use.
sourceDocument.Close();
mergedDocument.Close();
👉 Tip: Use using statements where possible to ensure proper disposal.
Issue 2: Missing Fonts or Layout Issues
Text in the merged PDF appears garbled, substituted with a fallback font, or missing entirely — even though the original files looked fine.
Why does this happen
PDFs may reference fonts without embedding them. When those fonts aren’t available in the runtime environment, the merged result can display incorrectly.
How to fix it
The most reliable solution is to ensure fonts are embedded in the source PDFs before merging. This guarantees consistent rendering across environments.
If you don’t control the source files, process and merge them using a consistent environment, and avoid relying on system-installed fonts.
PdfDocument mergedDocument = new PdfDocument();
foreach (string file in inputFiles)
{
PdfDocument temp = new PdfDocument(file);
mergedDocument.AppendPage(temp);
temp.Close();
}
mergedDocument.SaveToFile(outputFile);
mergedDocument.Close();
👉 In production, always test with real-world documents—not just simple PDFs.
Issue 3: Out of Memory When Merging Large Files
The merge works fine for small files, but throws a System.OutOfMemoryException — or causes noticeable memory pressure — when the input files are large or numerous.
Why does this happen
By default, loading a PdfDocument reads the entire file into memory. If you're merging ten 50MB PDFs, you're potentially holding 500MB in memory at once — before the output document is even written.
How to fix it
Process files one at a time and release each one before loading the next. Avoid holding all source documents open simultaneously:
PdfDocument merged = new PdfDocument();
foreach (string file in inputFiles)
{
using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
{
PdfDocument temp = new PdfDocument(fs);
merged.AppendPage(temp);
temp.Close();
}
}
merged.SaveToFile(outputFile);
merged.Close();
Going further
For very large batches, consider merging in chunks — combine files in groups of 5–10, write each chunk to a temp file, then do a final merge of the temp files. This keeps peak memory usage bounded regardless of input size.
Other Common Issues
Form fields and annotations disappear after merging
By default, merging flattens interactive elements. If you need to preserve form fields, set merged.FileInfo.IncrementalUpdate = true before appending pages, or explicitly copy the AcroForm structure from each source document.
Password-protected PDFs fail to merge
Spire.PDF will throw an exception if it encounters an encrypted file without credentials. Load protected files with the password explicitly: new PdfDocument(file, "yourpassword") — then append as normal.
Pages come out in the wrong order
This is almost always a file list ordering issue, not a library bug. Sort your input array explicitly before merging rather than relying on filesystem or directory enumeration order, which varies by OS and environment.
Advanced Scenarios
Once basic merging is working reliably, there are a few common extensions worth knowing about. You don’t need all of these for basic use—pick what fits your scenario.
Here's a minimal example you can copy and run:
Merging Specific Pages Only
Sometimes you don't need the entire document — just merge a range of pages from each source file.
using Spire.Pdf;
// Load source PDFs
PdfDocument[] pdfs = new PdfDocument[]
{
new PdfDocument("sample0.pdf"),
new PdfDocument("sample1.pdf"),
new PdfDocument("sample2.pdf")
};
// Create merged document
PdfDocument newPDF = new PdfDocument();
// Insert selected pages
newPDF.InsertPageRange(pdfs[0], 1, 2); // Pages 1-2 from first PDF
newPDF.InsertPage(pdfs[1], 0); // First page from second PDF
newPDF.InsertPage(pdfs[2], 1); // Second page from third PDF
// Save result
newPDF.SaveToFile("SelectivePageMerging.pdf");
This is useful when you're pulling specific sections from multiple reports into a single summary document.
Adding a Watermark During Merge
If you need to stamp each page with a watermark — a company name, "CONFIDENTIAL", a draft label — the merge step is a natural place to do it.
using Spire.Pdf;
using Spire.Pdf.Graphics;
using System.Drawing;
// Merge
var merged = PdfDocument.MergeFiles(new[] { "doc1.pdf", "doc2.pdf", "doc3.pdf" });
string tempPath = Path.GetTempFileName();
merged.Save(tempPath);
merged.Close();
// Add watermark
using (var pdf = new PdfDocument())
{
pdf.LoadFromFile(tempPath);
var font = new PdfTrueTypeFont(new Font("Arial", 50f, FontStyle.Bold), true);
foreach (var page in pdf.Pages)
{
page.Canvas.SetTransparency(0.5f);
page.Canvas.TranslateTransform(page.Canvas.Size.Width / 2, page.Canvas.Size.Height / 2);
page.Canvas.RotateTransform(-45);
page.Canvas.DrawString("CONFIDENTIAL", font, PdfBrushes.DarkGray, -150, -25);
page.Canvas.SetTransparency(1f);
}
pdf.SaveToFile("merged_with_watermark.pdf");
}
File.Delete(tempPath);
Adjust the transparency, font size, and position to suit your layout.
Adding Page Numbers After Merging
Page numbers should reflect the final document, not the originals — so add page numbers after the merge is complete.
using Spire.Pdf;
using Spire.Pdf.Graphics;
// Merge
var merged = PdfDocument.MergeFiles(new[] { @"D:\doc1.pdf", @"D:\doc2.pdf", @"D:\doc3.pdf" });
string temp = Path.GetTempFileName();
merged.Save(temp, FileFormat.PDF);
merged.Close();
// Add numbers
using (var pdf = new PdfDocument())
{
pdf.LoadFromFile(temp);
var font = new PdfTrueTypeFont(new Font("Arial", 9f), true);
int total = pdf.Pages.Count;
for (int i = 0; i < total; i++)
{
var page = pdf.Pages[i];
string text = $"Page {i + 1} of {total}";
float x = page.Canvas.ClientSize.Width - font.MeasureString(text).Width - 50f;
page.Canvas.DrawString(text, font, PdfBrushes.Black, x, page.Canvas.ClientSize.Height - 50f);
}
pdf.SaveToFile(@"D:\numbered_merged.pdf");
}
File.Delete(temp);
Exposing Merge as an ASP.NET Core API Endpoint
If you're building a service that accepts PDF uploads and returns a merged file, here's a minimal endpoint structure:
app.MapPost("/merge-stream", async (HttpRequest request) =>
{
var files = request.Form.Files;
if (files.Count < 2)
return Results.BadRequest("At least two files are required.");
PdfDocument merged = new PdfDocument();
try
{
foreach (var file in files)
{
using var stream = file.OpenReadStream();
// Load PDF from stream
using var tempDoc = new PdfDocument(stream);
// Insert all pages
for (int i = 0; i < tempDoc.Pages.Count; i++)
{
merged.InsertPage(tempDoc, i, i);
}
}
using var output = new MemoryStream();
merged.SaveToStream(output);
merged.Close();
output.Position = 0;
return Results.File(output.ToArray(), "application/pdf", "merged.pdf");
}
catch (Exception ex)
{
merged.Close();
return Results.Problem($"Error merging PDFs: {ex.Message}");
}
});
A few things to keep in mind for production use:
- Validate file types before processing
- Set a reasonable size limit per upload
- Consider running the merge in a background job for large files rather than blocking the request thread
Async Batch Processing for Large Volumes
For pipelines that process many files at once, avoid blocking threads on I/O. Here's a simple async wrapper:
public async Task<byte[]> MergePdfsAsync(IEnumerable<string> filePaths)
{
// Since Spire.PDF's MergeFiles doesn't support async I/O,
// you can omit the async wrapper in practice, or just add a caching layer
return await Task.Run(() =>
{
var filesArray = filePaths.ToArray();
// Use the officially recommended MergeFiles method
using var mergedDocument = PdfDocument.MergeFiles(filesArray);
using var output = new MemoryStream();
mergedDocument.SaveToStream(output);
mergedDocument.Close();
return output.ToArray();
});
}
For high-throughput scenarios, pair this with a queue (e.g., Azure Service Bus or a simple [Channel<T>](https://learn.microsoft.com/en-us/dotnet/core/extensions/channels)) to process merge jobs one at a time rather than in parallel — PDF merging is memory-intensive enough that concurrency can backfire.
Alternative Libraries
Spire.PDF works well for the scenarios covered in this guide, but it's not the only option. Depending on your project's requirements — licensing, budget, or specific feature needs — one of these alternatives may be a better fit.
| Library | License | Highlights | Best For |
|---|---|---|---|
| Spire.PDF | Commercial (free tier available) | Clean API, good documentation, server-friendly | General-purpose merging, teams that want a supported library |
| iText 7 | AGPL / Commercial | Industry standard, highly capable, large community | Complex PDF manipulation, enterprise projects with commercial license |
| PDFsharp | MIT | Lightweight, no cost, simple API | Open-source projects, basic merging without advanced requirements |
| PdfPig | Apache 2.0 | Fully open-source, actively maintained | Reading and extracting PDF content, lightweight merging |
| Aspose.PDF | Commercial | Feature-rich, excellent format support | Teams that need broad format coverage and vendor support |
👉 Quick recommendation:
- Use Spire.PDF for ease of use and quick integration.
- Use iText 7 if licensing is not an issue.
- Use PDFsharp for simple open-source scenarios.
Each of these libraries can handle PDF merging—the right choice depends on your licensing constraints and how complex your use case is.
Conclusion
Merging PDFs in .NET is straightforward—until real-world constraints come into play.
By using a library that doesn't depend on Microsoft Office and understanding the common pitfalls, you can build a solution that's both reliable and production-ready.
Once you have the basics in place, you can extend the same approach to more advanced scenarios like batching, APIs, or document workflows.
Start with a simple merge, validate it with real-world files, and build from there.




Top comments (0)