DataTable column headers and footers can be combined for grouped display.
<div class="card">
    <h:form>
        <div class="flex justify-content-between">
           <div>
           </div>
           <div>
              <p:commandButton value="XLSX" styleClass="mr-2 mb-2" >
                  <p:dataExporter type="xlsxstream" target="tblSales tblPlayers" fileName="grouped"/>
              </p:commandButton>
              <p:commandButton value="PDF" styleClass="mr-2 mb-2" >
                  <p:dataExporter type="pdf" target="tblSales tblPlayers" fileName="grouped"/>
              </p:commandButton>
           </div>
        </div>
        <p:dataTable id="tblSales" var="sale" value="#{dtGroupView.sales}">
            <f:facet name="header">
                <h:outputText value="Product Sales"/>
            </f:facet>
            <p:columnGroup type="header">
                <p:row>
                    <p:column rowspan="3" headerText="Product"/>
                    <p:column colspan="4" headerText="Sale Rate"/>
                </p:row>
                <p:row>
                    <p:column colspan="2" headerText="Sales"/>
                    <p:column colspan="2" headerText="Profit"/>
                </p:row>
                <p:row>
                    <p:column headerText="Last Year"/>
                    <p:column headerText="This Year"/>
                    <p:column headerText="Last Year"/>
                    <p:column headerText="This Year"/>
                </p:row>
            </p:columnGroup>
            <p:column>
                <h:outputText value="#{sale.manufacturer}"/>
            </p:column>
            <p:column>
                <h:outputText value="#{sale.lastYearProfit}%"/>
            </p:column>
            <p:column>
                <h:outputText value="#{sale.thisYearProfit}%"/>
            </p:column>
            <p:column>
                <h:outputText value="#{sale.lastYearSale}">
                    <f:convertNumber type="currency" />
                </h:outputText>
            </p:column>
            <p:column>
                <h:outputText value="#{sale.thisYearSale}">
                    <f:convertNumber type="currency" />
                </h:outputText>
            </p:column>
            <p:columnGroup type="footer">
                <p:row>
                    <p:column colspan="3" style="text-align:right" footerText="Totals:"/>
                    <p:column>
                        <f:facet name="footer">
                            <h:outputText value="#{dtGroupView.lastYearTotal}">
                                <f:convertNumber type="currency" currencySymbol="$"/>
                            </h:outputText>
                        </f:facet>
                    </p:column>
                    <p:column>
                        <f:facet name="footer">
                            <h:outputText value="#{dtGroupView.thisYearTotal}">
                                <f:convertNumber type="currency" currencySymbol="$"/>
                            </h:outputText>
                        </f:facet>
                    </p:column>
                </p:row>
            </p:columnGroup>
            <f:facet name="footer">
                <h:outputText value="Data between 2013-2014"/>
            </f:facet>
        </p:dataTable>
        <p:dataTable id="tblPlayers" var="player" value="#{dtGroupView.players}" style="margin-top:40px">
            <f:facet name="header">
                <h:outputText value="Dynamic Columns"/>
            </f:facet>
            <p:columnGroup type="header">
                <p:row>
                    <p:column rowspan="2" headerText="Player"/>
                    <p:column colspan="#{dtGroupView.yearCount}" headerText="Goals"/>
                </p:row>
                <p:row>
                    <p:columns var="year" value="#{dtGroupView.years}" sortBy="#{player.getGoals(year)}" filterBy="#{player.getGoals(year)}" headerText="#{year}"/>
                </p:row>
            </p:columnGroup>
            <p:column>
                <h:outputText value="#{player.name}"/>
            </p:column>
            <p:columns value="#{dtGroupView.years}" var="year">
                <h:outputText value="#{player.getGoals(year)}"/>
            </p:columns>
            <f:facet name="footer">
                <h:outputText value="Data between 2010-2014" styleClass="font-bold" />
            </f:facet>
        </p:dataTable>
    </h:form>
</div>
package org.primefaces.showcase.view.data.datatable;
import org.primefaces.showcase.domain.Player;
import org.primefaces.showcase.domain.Sale;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import jakarta.annotation.PostConstruct;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Named;
@Named("dtGroupView")
@ViewScoped
public class GroupView implements Serializable {
    private static final String[] MANUFACTORS;
    private static final String[] PLAYER_NAMES;
    private List<Sale> sales;
    private Integer lastYearTotal;
    private Integer thisYearTotal;
    private List<Integer> years;
    private List<Player> players;
    static {
        MANUFACTORS = new String[10];
        MANUFACTORS[0] = "Bamboo Watch";
        MANUFACTORS[1] = "Black Watch";
        MANUFACTORS[2] = "Blue Band";
        MANUFACTORS[3] = "Blue T-Shirt";
        MANUFACTORS[4] = "Brown Purse";
        MANUFACTORS[5] = "Chakra Bracelet";
        MANUFACTORS[6] = "Galaxy Earrings";
        MANUFACTORS[7] = "Game Controller";
        MANUFACTORS[8] = "Gaming Set";
        MANUFACTORS[9] = "Gold Phone Case";
        PLAYER_NAMES = new String[10];
        PLAYER_NAMES[0] = "Lionel Messi";
        PLAYER_NAMES[1] = "Cristiano Ronaldo";
        PLAYER_NAMES[2] = "Arjen Robben";
        PLAYER_NAMES[3] = "Franck Ribery";
        PLAYER_NAMES[4] = "Ronaldinho";
        PLAYER_NAMES[5] = "Luis Suarez";
        PLAYER_NAMES[6] = "Sergio Aguero";
        PLAYER_NAMES[7] = "Zlatan Ibrahimovic";
        PLAYER_NAMES[8] = "Neymar Jr";
        PLAYER_NAMES[9] = "Andres Iniesta";
    }
    @PostConstruct
    public void init() {
        sales = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            sales.add(new Sale(MANUFACTORS[i], getRandomAmount(), getRandomAmount(), getRandomPercentage(), getRandomPercentage()));
        }
        years = new ArrayList<>();
        years.add(2010);
        years.add(2011);
        years.add(2012);
        years.add(2013);
        years.add(2014);
        players = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            players.add(new Player(PLAYER_NAMES[i], generateRandomGoalStatsData()));
        }
    }
    public List<Sale> getSales() {
        return sales;
    }
    private int getRandomAmount() {
        return (int) (Math.random() * 100000);
    }
    private int getRandomPercentage() {
        return (int) (Math.random() * 100);
    }
    public Integer getLastYearTotal() {
        if (lastYearTotal == null) {
            lastYearTotal = sales.stream().mapToInt(Sale::getLastYearSale).sum();
        }
        return lastYearTotal;
    }
    public Integer getThisYearTotal() {
        if (thisYearTotal == null) {
            thisYearTotal = sales.stream().mapToInt(Sale::getThisYearSale).sum();
        }
        return thisYearTotal;
    }
    public List<Integer> getYears() {
        return years;
    }
    public int getYearCount() {
        return years.size();
    }
    public List<Player> getPlayers() {
        return players;
    }
    private Map<Integer, Integer> generateRandomGoalStatsData() {
        Map<Integer, Integer> stats = new LinkedHashMap<>();
        for (int i = 0; i < 5; i++) {
            stats.put(years.get(i), getRandomGoals());
        }
        return stats;
    }
    private int getRandomGoals() {
        return (int) (Math.random() * 50);
    }
}
package org.primefaces.showcase.domain;
import java.io.Serializable;
public class Sale implements Serializable {
    private String manufacturer;
    private int lastYearSale;
    private int thisYearSale;
    private int lastYearProfit;
    private int thisYearProfit;
    public Sale() {
    }
    public Sale(String manufacturer, int lastYearSale, int thisYearSale, int lastYearProfit, int thisYearProfit) {
        this.manufacturer = manufacturer;
        this.lastYearSale = lastYearSale;
        this.thisYearSale = thisYearSale;
        this.lastYearProfit = lastYearProfit;
        this.thisYearProfit = thisYearProfit;
    }
    public int getLastYearProfit() {
        return lastYearProfit;
    }
    public void setLastYearProfit(int lastYearProfit) {
        this.lastYearProfit = lastYearProfit;
    }
    public int getLastYearSale() {
        return lastYearSale;
    }
    public void setLastYearSale(int lastYearSale) {
        this.lastYearSale = lastYearSale;
    }
    public String getManufacturer() {
        return manufacturer;
    }
    public void setManufacturer(String manufacturer) {
        this.manufacturer = manufacturer;
    }
    public int getThisYearProfit() {
        return thisYearProfit;
    }
    public void setThisYearProfit(int thisYearProfit) {
        this.thisYearProfit = thisYearProfit;
    }
    public int getThisYearSale() {
        return thisYearSale;
    }
    public void setThisYearSale(int thisYearSale) {
        this.thisYearSale = thisYearSale;
    }
}
package org.primefaces.showcase.domain;
import java.util.LinkedHashMap;
import java.util.Map;
public class Player {
    private String name;
    private Map<Integer, Integer> goals;
    public Player() {
        goals = new LinkedHashMap<>();
    }
    public Player(String name, Map<Integer, Integer> goals) {
        this.name = name;
        this.goals = goals;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getGoals(int year) {
        return goals.get(year);
    }
}