Legacy notice!

iText 5 is the previous major version of iText’s leading PDF SDK. iText 5 is EOL, and is no longer developed, although we still provide support and security fixes. Switch your project to iText 8, our latest version which supports the latest PDF standards and technologies.
Check related iText 8 content!

I need to create a TOC (not bookmarks) at the beginning of this document with clickable links to the first pages of each of the source PDFs.

I'm doing the following:

Document document = new Document();
PdfCopy copy = new PdfCopy(document, outputStream);
PdfCopy.PageStamp stamp;
PdfReader reader;
<ListInputStream> pdfs = streamOfPDFFiles;
<ListPdfReader> readers = new ArrayListPdfReader>();
<IteratorInputStream> iteratorPDFs = pdfs.iterator();
for (; iteratorPDFs.hasNext(); pdfCounter++) {
    InputStream pdf = iteratorPDFs.next();
    reader = new PdfReader(pdf);
int currentPageNumber = 0;
<IteratorPdfReader> readerIterator = readers.iterator();
PdfImportedPage page;
int count = 1;
while (readerIterator.hasNext()) {
    reader = readerIterator.next();
    int number_of_pages = reader.getNumberOfPages();
    for (int pageNum = 0; pageNum  number_of_pages;) {
        page = copy.getImportedPage(reader, ++pageNum);
            Element.ALIGN_RIGHT, new Phrase(
                String.format("%d", currentPageNumber),
                new Font(FontFamily.TIMES_ROMAN,3)), 50, 50, 0);
Posted on StackOverflow on Feb 4, 2014 by Butani Vijay

You're asking for something that should be trivial, but that isn't. Please take a look at the MergeWithToc example. You'll see that your code to merge PDFs is correct, but in my example, I added one extra feature:

chunk = new Chunk(String.format("Page %d", pageNo));
if (i == 1)
    chunk.setLocalDestination("p" + pageNo);
    Element.ALIGN_RIGHT, new Phrase(chunk), 559, 810, 0);

For every first page, I define a named destination as a local destination. We use p followed by the page number as its name.

We'll use these named destinations in an extra page that will serve as a TOC:

PdfReader reader = new PdfReader(SRC3);
page = copy.getImportedPage(reader, 1);
stamp = copy.createPageStamp(page);
Paragraph p;
PdfAction action;
PdfAnnotation link;
float y = 770;
ColumnText ct = new ColumnText(stamp.getOverContent());
ct.setSimpleColumn(36, 36, 559, y);
for (Map.Entry entry : toc.entrySet()) {
    p = new Paragraph(entry.getValue());
    p.add(new Chunk(new DottedLineSeparator()));
    action = PdfAction.gotoLocalPage("p" + entry.getKey(), false);
    link = new PdfAnnotation(copy, 36, ct.getYLine(), 559, y, action);
    y = ct.getYLine();

In my example, I assume that the TOC fits on a single page. You'll have to keep track of the y value and create a new page if its value is lower than the bottom margin.

If you want the TOC to be the first page, you need to reorder the pages in a second go. This is shown in the MergeWithToc2 example:

reader = new PdfReader(baos.toByteArray());
n = reader.getNumberOfPages();
reader.selectPages(String.format("%d, 1-%d", n, n-1));
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(filename));