Index: src/main/java/org/apache/pdfbox/cos/COSName.java
===================================================================
--- src/main/java/org/apache/pdfbox/cos/COSName.java	(revision 1499362)
+++ src/main/java/org/apache/pdfbox/cos/COSName.java	(working copy)
@@ -930,6 +930,14 @@
     /**
      * A common COSName value.
      */
+    public static final COSName STRUCTPARENT = new COSName( "StructParent" );
+    /**
+     * A common COSName value.
+     */
+    public static final COSName STRUCTPARENTS = new COSName( "StructParents" );
+    /**
+     * A common COSName value.
+     */
     public static final COSName METADATA = new COSName(  "Metadata" );
     /**
      * A common COSName value.
@@ -1107,6 +1115,10 @@
     /**
      * A common COSName value.
      */
+    public static final COSName PARENT_TREE = new COSName( "ParentTree" );
+    /**
+     * A common COSName value.
+     */
     public static final COSName PARENT_TREE_NEXT_KEY = new COSName("ParentTreeNextKey");
     /**
     * A common COSName value.
Index: src/main/java/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureTreeRoot.java
===================================================================
--- src/main/java/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureTreeRoot.java	(revision 1499362)
+++ src/main/java/org/apache/pdfbox/pdmodel/documentinterchange/logicalstructure/PDStructureTreeRoot.java	(working copy)
@@ -20,11 +20,13 @@
 import java.util.Hashtable;
 import java.util.Map;
 
+import org.apache.pdfbox.cos.COSArray;
 import org.apache.pdfbox.cos.COSBase;
 import org.apache.pdfbox.cos.COSDictionary;
 import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.pdmodel.common.COSDictionaryMap;
 import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
+import org.apache.pdfbox.pdmodel.common.PDNumberTreeNode;
 
 /**
  * A root of a structure tree.
@@ -60,6 +62,51 @@
 
 
     /**
+     * Returns the K array entry.
+     *
+     * @return the K array entry
+     */
+     public COSArray getKArray()
+     {
+         COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K);
+         if (k != null)
+         {
+             if (k instanceof COSDictionary)
+             {
+                 COSDictionary kdict = (COSDictionary)k;
+                 k = kdict.getDictionaryObject(COSName.K);
+                 if (k instanceof COSArray)
+                 {
+                     return (COSArray)k;
+                 }
+             }
+             else
+             {
+                 return (COSArray)k;
+             }
+         }
+         return null;
+     }
+     /**
+      * Returns the K entry.
+      * 
+      * @return the K entry
+      */
+     public COSBase getK()
+     {
+         return this.getCOSDictionary().getDictionaryObject(COSName.K);
+     }
+     /**
+      * Sets the K entry.
+      * 
+      * @param k the K value
+      */
+     public void setK(COSBase k)
+     {
+         this.getCOSDictionary().setItem(COSName.K, k);
+     }
+
+    /**
      * Returns the ID tree.
      * 
      * @return the ID tree
@@ -86,6 +133,31 @@
     }
 
     /**
+     * Returns the parent tree.
+     *
+     * @return the parent tree
+     */
+     public PDNumberTreeNode getParentTree()
+     {
+        COSDictionary parentTreeDic = (COSDictionary) this.getCOSDictionary().getDictionaryObject(COSName.PARENT_TREE);
+        if (parentTreeDic != null)
+        {
+            return new PDNumberTreeNode(parentTreeDic, COSBase.class);
+        }
+        return null;
+     }
+
+    /**
+     * Sets the parent  tree.
+     * 
+     * @param parentTree the parent tree
+     */
+     public void setParentTree(PDNumberTreeNode parentTree)
+     {
+        this.getCOSDictionary().setItem(COSName.PARENT_TREE, parentTree);
+     }
+
+    /**
      * Returns the next key in the parent tree.
      * 
      * @return the next key in the parent tree
@@ -96,6 +168,16 @@
     }
 
     /**
+     * Sets the next key in the parent tree.
+     * 
+     * @param parentTreeNextkey the next key in the parent tree.
+     */
+    public void setParentTreeNextKey(int parentTreeNextkey)
+    {
+        this.getCOSDictionary().setInt(COSName.PARENT_TREE_NEXT_KEY, parentTreeNextkey);
+    }
+
+    /**
      * Returns the role map.
      * 
      * @return the role map
Index: src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java
===================================================================
--- src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java	(revision 1499362)
+++ src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotation.java	(working copy)
@@ -566,6 +566,24 @@
     }
 
     /**
+     * This will get the StructParent for this annotation.
+     */
+    public int getStructParent()
+    {
+        return getDictionary().getInt(COSName.STRUCTPARENT, 0);
+    }
+
+    /**
+     * This will set the StructParent for this page.
+     *
+     * @param structParents The new StructParent for this page.
+     */
+    public void setStructParent(int structParent)
+    {
+        getDictionary().setInt(COSName.STRUCTPARENT, structParent);
+    }
+
+    /**
      * This will set the colour used in drawing various elements.
      * As of PDF 1.6 these are : Background of icon when closed
      *                           Title bar of popup window
Index: src/main/java/org/apache/pdfbox/pdmodel/PDPage.java
===================================================================
--- src/main/java/org/apache/pdfbox/pdmodel/PDPage.java	(revision 1499362)
+++ src/main/java/org/apache/pdfbox/pdmodel/PDPage.java	(working copy)
@@ -345,6 +345,25 @@
     }
 
     /**
+     * This will get the StructParents for this page.
+     */
+    public int getStructParents()
+    {
+        return page.getInt(COSName.STRUCTPARENTS, 0);
+    }
+
+    /**
+     * This will set the StructParents for this page.
+     *
+     * @param structParents The new StructParents for this page.
+     */
+    public void setStructParents(int structParents)
+    {
+      page.setInt(COSName.STRUCTPARENTS, structParents);
+    }
+
+
+    /**
      * A rectangle, expressed in default user space units,
      * defining the visible region of default user space. When the page is displayed
      * or printed, its contents are to be clipped (cropped) to this rectangle
Index: src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java
===================================================================
--- src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java	(revision 1499362)
+++ src/main/java/org/apache/pdfbox/util/PDFMergerUtility.java	(working copy)
@@ -22,15 +22,18 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 
 import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
 import org.apache.pdfbox.cos.COSDictionary;
 import org.apache.pdfbox.cos.COSInteger;
 import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.cos.COSNumber;
 import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSString;
 import org.apache.pdfbox.exceptions.COSVisitorException;
 import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
@@ -39,6 +42,10 @@
 import org.apache.pdfbox.pdmodel.PDPage;
 import org.apache.pdfbox.pdmodel.common.COSArrayList;
 import org.apache.pdfbox.pdmodel.common.PDStream;
+import org.apache.pdfbox.pdmodel.common.PDNumberTreeNode;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDMarkInfo;
+import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
 import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
@@ -54,6 +61,7 @@
  */
 public class PDFMergerUtility
 {
+    public static final String STRUCTURETYPE_DOCUMENT = "Document";
 
     private List<InputStream> sources;
     private String destinationFileName;
@@ -395,9 +403,63 @@
             destCatalog.getCOSDictionary().setItem( COSName.METADATA, newStream );
         }
 
+        // merge logical structure hierarchy if logical structure information is available in both source pdf and destination pdf
+        boolean mergeStructTree = false;
+        int destParentTreeNextKey = -1;
+        COSDictionary destParentTreeDict = null;
+        COSDictionary srcParentTreeDict = null;
+        COSArray destNumbersArray = null;
+        COSArray srcNumbersArray = null;
+        PDMarkInfo destMark = destCatalog.getMarkInfo();
+        PDStructureTreeRoot destStructTree = destCatalog.getStructureTreeRoot();
+        PDMarkInfo srcMark = srcCatalog.getMarkInfo();
+        PDStructureTreeRoot srcStructTree = srcCatalog.getStructureTreeRoot();
+        if ( destStructTree != null )
+        {
+            PDNumberTreeNode destParentTree = destStructTree.getParentTree();
+            destParentTreeNextKey = destStructTree.getParentTreeNextKey();
+            if ( destParentTree != null )
+            {
+                destParentTreeDict = destParentTree.getCOSDictionary();
+                destNumbersArray = (COSArray)destParentTreeDict.getDictionaryObject( COSName.NUMS );
+                if ( destNumbersArray != null )
+                {
+                    if ( destParentTreeNextKey < 0 )
+                    {
+                        destParentTreeNextKey = destNumbersArray.size() / 2;
+                    }
+                    if ( destParentTreeNextKey > 0 )
+                    {
+                        if ( srcStructTree != null )
+                        {
+                            PDNumberTreeNode srcParentTree = srcStructTree.getParentTree();
+                            if ( srcParentTree != null )
+                            {
+                                srcParentTreeDict = srcParentTree.getCOSDictionary();
+                                srcNumbersArray = (COSArray)srcParentTreeDict.getDictionaryObject( COSName.NUMS );
+                                if ( srcNumbersArray != null )
+                                {
+                                    mergeStructTree = true;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            if ( destMark != null && destMark.isMarked() && !mergeStructTree )
+            {
+                destMark.setMarked( false );
+            }
+            if ( !mergeStructTree )
+            {
+                destCatalog.setStructureTreeRoot( null );
+            }
+        }
+
         //finally append the pages
         List<PDPage> pages = srcCatalog.getAllPages();
         Iterator<PDPage> pageIter = pages.iterator();
+        HashMap<COSDictionary, COSDictionary> objMapping = new HashMap<COSDictionary, COSDictionary>();
         while( pageIter.hasNext() )
         {
             PDPage page = pageIter.next();
@@ -406,8 +468,53 @@
             newPage.setCropBox( page.findCropBox() );
             newPage.setMediaBox( page.findMediaBox() );
             newPage.setRotation( page.findRotation() );
+            if ( mergeStructTree )
+            {
+                updateStructParentEntries(newPage, destParentTreeNextKey);
+                objMapping.put( page.getCOSDictionary(), newPage.getCOSDictionary() );
+                List<PDAnnotation> oldAnnots = page.getAnnotations();
+                List<PDAnnotation> newAnnots = newPage.getAnnotations();
+                for (int i=0; i<oldAnnots.size(); i++) 
+                {
+                    objMapping.put( oldAnnots.get(i).getDictionary(), newAnnots.get(i).getDictionary() );
+                }
+            }
             destination.addPage( newPage );
         }
+        if ( mergeStructTree )
+        {
+            int srcPageCount = pages.size();
+            updatePageReferences( srcNumbersArray, objMapping );
+            for ( int i=0; i<srcNumbersArray.size()/2; i++ )
+            {
+                destNumbersArray.add( COSInteger.get( destParentTreeNextKey+i) );
+                destNumbersArray.add( srcNumbersArray.getObject( i*2+1 ) );
+            }
+            destParentTreeNextKey += srcNumbersArray.size()/2;
+            destParentTreeDict.setItem(COSName.NUMS, destNumbersArray);
+            PDNumberTreeNode newParentTreeNode = new PDNumberTreeNode( destParentTreeDict, COSBase.class );
+            destStructTree.setParentTree( newParentTreeNode );
+            destStructTree.setParentTreeNextKey( destParentTreeNextKey );
+
+            COSDictionary kDictLevel0 = new COSDictionary();
+            COSArray newKArray = new COSArray();
+            COSArray destKArray = destStructTree.getKArray();
+            COSArray srcKArray = srcStructTree.getKArray();
+            if ( destKArray != null && srcKArray != null )
+            {
+                updateParentEntry( destKArray, kDictLevel0 );
+                newKArray.addAll( destKArray );
+                if ( mergeStructTree )
+                {
+                    updateParentEntry( srcKArray, kDictLevel0 );
+                }
+                newKArray.addAll( srcKArray );
+            }
+            kDictLevel0.setItem( COSName.K, newKArray );
+            kDictLevel0.setItem( COSName.P, destStructTree );
+            kDictLevel0.setItem( COSName.S, new COSString( STRUCTURETYPE_DOCUMENT ) );
+            destStructTree.setK( kDictLevel0 );
+        }
     }
 
 
@@ -463,5 +570,96 @@
         this.ignoreAcroFormErrors = ignoreAcroFormErrors;
     }
 
+    /**
+     * Update the Pg and Obj references to the new (merged) page.
+     *
+     * @param parentTreeEntry 
+     * @param objMapping mapping between old and new references
+     */
+    private void updatePageReferences( COSDictionary parentTreeEntry, HashMap<COSDictionary,COSDictionary> objMapping )
+    {
+        COSBase page = parentTreeEntry.getDictionaryObject( COSName.PG );
+        if ( page != null && page instanceof COSDictionary )
+        {
+            if ( objMapping.containsKey( page ) )
+            {
+                parentTreeEntry.setItem( COSName.PG, objMapping.get( page ) );
+            }
+        }
+        COSBase obj = parentTreeEntry.getDictionaryObject( COSName.OBJ );
+        if ( obj != null && obj instanceof COSDictionary )
+        {
+            if ( objMapping.containsKey( obj ) )
+            {
+                parentTreeEntry.setItem( COSName.OBJ, objMapping.get( obj ) );
+            }
+        }
+        COSBase kSubEntry = parentTreeEntry.getDictionaryObject( COSName.K );
+        if ( kSubEntry instanceof COSArray )
+        {
+            updatePageReferences( (COSArray)kSubEntry, objMapping );
+        }
+        else if ( kSubEntry instanceof COSDictionary )
+        {
+            updatePageReferences( (COSDictionary)kSubEntry, objMapping );
+        }
+    }
 
+    private void updatePageReferences( COSArray parentTreeEntry, HashMap<COSDictionary,COSDictionary> objMapping )
+    {
+        for ( int i=0; i<parentTreeEntry.size(); i++ )
+        {
+            COSBase subEntry = parentTreeEntry.getObject( i );
+            if ( subEntry instanceof COSArray )
+            {
+                updatePageReferences( (COSArray)subEntry, objMapping );
+            }
+            else if ( subEntry instanceof COSDictionary )
+            {
+                updatePageReferences( (COSDictionary)subEntry, objMapping );
+            }
+        }
+    }
+
+    /**
+     * Update the P reference to the new parent dictionary.
+     *
+     * @param kArray the kids array
+     * @param newParent the new parent
+     */
+    private void updateParentEntry( COSArray kArray, COSDictionary newParent )
+    {
+        for ( int i=0; i<kArray.size(); i++ )
+        {
+            COSBase subEntry = kArray.getObject( i );
+            if ( subEntry instanceof COSDictionary )
+            {
+                COSDictionary dictEntry = (COSDictionary)subEntry;
+                if ( dictEntry.getDictionaryObject( COSName.P ) != null )
+                {
+                    dictEntry.setItem( COSName.P, newParent );
+                }
+            }
+        }
+    }
+
+    /**
+     * Update the StructParents and StructParent values in a PDPage.
+     *
+     * @param page the new page 
+     * @param structParentOffset the offset which should be applied
+     */
+    private void updateStructParentEntries( PDPage page, int structParentOffset ) throws IOException
+    {
+        page.setStructParents( page.getStructParents() + structParentOffset );
+        List<PDAnnotation> annots = page.getAnnotations();
+        List<PDAnnotation> newannots = new ArrayList<PDAnnotation>();
+        for ( PDAnnotation annot : annots )
+        {
+            annot.setStructParent( annot.getStructParent() + structParentOffset );
+            newannots.add(annot);
+        }
+        page.setAnnotations(newannots);
+    }
+
 }
