We're sorry Aspose doesn't work properply without JavaScript enabled.

Free Support Forum - aspose.com

Aspose PDF mail merge doesn't work

Hi Team,
TEST.zip (283.2 KB)

Please find attached the sample code and docs. I have created a word document(Test.docx) with MergeFields and converted that to PDF(Test_converted.pdf). In my Java code I read the PDF, converted that to word and tried mail merge which doesn’t work. Can you please let me know what goes wrong here?

FYI, The word document that I created(Test.docx) works as expected when I do mail merge.

Our requirement here is to read PDF file and mail merge template fields.


Please also ZIP and upload your input Word document (Test.docx file) here for further testing. We will then investigate the issue on our end and provide you more information.

Hi Awias,

It wasn’t the word document but the RTF document(Test.rtf) which was working and its conversion to PDF(Test_Converted.pdf) which wasn’t working. These two are already attached.


The problem occurs because Aspose.PDF does not write actual MERGEFIELDs during converting PDF to DOCX (Word documents). But you can turn those static texts to actual MERGEFIELDs and then execute the mail merge operation. Please see the following workaround:

com.aspose.pdf.Document pdfDoc = new com.aspose.pdf.Document("E:\\temp\\test\\TEST_Converted.pdf");
ByteArrayOutputStream baOs = new ByteArrayOutputStream();
//Converting PDF document to word document
pdfDoc.save(baOs, com.aspose.pdf.SaveFormat.DocX);

String[] referenceFields = {"FIRSTNAME", "MEMFIRST"};
Object[] referenceValues = {"AAA", "BBB"};

Document document = mailMergeTemplate(baOs.toByteArray(), referenceFields, referenceValues);

private static Document mailMergeTemplate(byte[] templateFile, String[] referenceFields,
                                          Object[] referenceValues) throws Exception {
    Document doc = null;
    try {
        doc = new Document(new ByteArrayInputStream(templateFile));

        FindReplaceOptions opts = new FindReplaceOptions();
        opts.setReplacingCallback( new ReplaceEvaluatorFindAndInsertMergefield());

        doc.getRange().replace(Pattern.compile("«(.*?)»"), "", opts);

        doc.getMailMerge().setFieldMergingCallback(new HandleMergeFields());
        doc.getMailMerge().execute(referenceFields, referenceValues);
    } catch (Exception e) {
    return doc;

 * This is called when mail merge engine encounters plain text (non image) merge
static class HandleMergeFields implements IFieldMergingCallback {
    public void fieldMerging(FieldMergingArgs args) throws Exception {
        System.out.println("Mail merge for field : " +args.getFieldName() + " & Value : " +args.getFieldValue());

     * This is called when mail merge engine encounters Image:XXX merge
     * field in the document. You have a chance to return an Image object,
     * file name or a stream that contains the image.
    public void imageFieldMerging(ImageFieldMergingArgs e) throws Exception {
        System.out.println("Mail merge for field : " +e.getFieldName() + " & Value : " +e.getFieldValue());

static class ReplaceEvaluatorFindAndInsertMergefield implements IReplacingCallback {

    public int replacing(ReplacingArgs e) throws Exception {
        // This is a Run node that contains either the beginning or the complete match.
        Node currentNode = e.getMatchNode();

        // The first (and may be the only) run can contain text before the match,
        // in this case it is necessary to split the run.
        if (e.getMatchOffset() > 0)
            currentNode = splitRun((Run) currentNode, e.getMatchOffset());

        ArrayList runs = new ArrayList();

        // Find all runs that contain parts of the match string.
        int remainingLength = e.getMatch().group().length();
        while ((remainingLength > 0) && (currentNode != null) && (currentNode.getText().length() <= remainingLength)) {
            remainingLength = remainingLength - currentNode.getText().length();

            // Select the next Run node.
            // Have to loop because there could be other nodes such as BookmarkStart etc.
            do {
                currentNode = currentNode.getNextSibling();
            } while ((currentNode != null) && (currentNode.getNodeType() != NodeType.RUN));

        // Split the last run that contains the match if there is any text left.
        if ((currentNode != null) && (remainingLength > 0)) {
            splitRun((Run) currentNode, remainingLength);

        //Change static text to real merge fields.
        DocumentBuilder builder = new DocumentBuilder((Document) e.getMatchNode().getDocument());
        builder.moveTo((Run) runs.get(runs.size() - 1));
        builder.insertField("MERGEFIELD \"" + e.getMatch().group(1) + "\"");

        for (Run run : (Iterable<Run>) runs)

        // Signal to the replace engine to do nothing because we have already done all what we wanted.
        return ReplaceAction.SKIP;

     * Splits text of the specified run into two runs. Inserts the new run just
     * after the specified run.
    private Run splitRun(Run run, int position) throws Exception {
        Run afterRun = (Run) run.deepClone(true);
        run.setText(run.getText().substring((0), (0) + (position)));
        run.getParentNode().insertAfter(afterRun, run);
        return afterRun;

Hope, this helps.