DEV Community

Cover image for How to Merge PDFs in .NET Without Microsoft Office (Avoid Common Issues)
Jen
Jen

Posted on

How to Merge PDFs in .NET Without Microsoft Office (Avoid Common Issues)

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
Enter fullscreen mode Exit fullscreen mode

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);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Merge PDF in Csharp

What's happening here:

  • PdfDocument.MergeFiles() method to merge multiple PDFs into a single file.
  • pdf.Save writes 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();
Enter fullscreen mode Exit fullscreen mode

👉 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();
Enter fullscreen mode Exit fullscreen mode

👉 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();
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

Merging Specific Pages Only in Csharp

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);
Enter fullscreen mode Exit fullscreen mode

Adding a Watermark During Merge

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);
Enter fullscreen mode Exit fullscreen mode

Adding Page Numbers After Merging

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}");
    }
});
Enter fullscreen mode Exit fullscreen mode

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();
    });
}
Enter fullscreen mode Exit fullscreen mode

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)