Uma das funcionalidades mais comuns em sistemas corporativos é a exportação de dados para planilhas do Microsoft Excel. Seja para gerar relatórios financeiros, listas de clientes, notas fiscais, controles de estoque ou outros tipos de relatórios, é comum encontrarmos aplicações de gestão com alguma opção de “Exportar para Excel”.

Embora existam diversos formatos utilizados para compartilhar informações, como CSV, PDF e JSON, o Excel continua sendo uma ferramenta bastante utilizada pelos usuários finais. Isso acontece porque, além de visualizar os dados, é possível realizar filtros, ordenações, cálculos, criar gráficos e executar diversas outras análises sem a necessidade de conhecimentos técnicos em programação.

No ecossistema Java, uma das principais bibliotecas para trabalhar com arquivos do Microsoft Office é o Apache POI, um projeto open source mantido pela Apache Software Foundation.

O Apache POI fornece APIs que permitem criar, modificar e ler arquivos de diferentes formatos utilizados pelo Microsoft Office, incluindo documentos do Word, apresentações do PowerPoint e planilhas do Excel, utilizando código Java.

Neste artigo, nosso foco será o uso do Apache POI para trabalhar com planilhas do Microsoft Excel no formato XLSX.

Para trabalhar com arquivos do Excel, o Apache POI disponibiliza diferentes APIs e classes, sendo que algumas delas são específicas para os formatos XLS e XLSX.

APIFormato
HSSFArquivos XLS (Excel 97-2003)
XSSFArquivos XLSX (Excel 2007+)
SXSSFEscrita de grandes arquivos XLSX utilizando streaming e menor consumo de memória

Na maioria das aplicações que trabalham com arquivos Excel atualmente, utilizaremos a API XSSF, que é destinada ao formato XLSX, utilizado pelas versões modernas do Microsoft Excel.

Quando precisamos gerar arquivos XLSX muito grandes, contendo centenas de milhares ou até milhões de linhas, podemos utilizar a API SXSSF. Ela utiliza um modelo de streaming que mantém apenas uma quantidade limitada de linhas na memória, enquanto o restante dos dados é armazenado em arquivos temporários durante a geração da planilha. Dessa forma, é possível reduzir significativamente o consumo de memória.

Neste primeiro momento, utilizaremos apenas a API XSSF.

Adicionando o Apache POI em um projeto Maven

O Apache POI está disponível no Maven Central, e sua utilização em um projeto Maven é bastante simples.

Para adicioná-lo ao projeto, basta incluir a dependência abaixo no arquivo pom.xml:

<dependencies>

    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.4.1</version>
    </dependency>

</dependencies>

A dependência poi-ooxml já disponibiliza as classes necessárias para trabalhar com arquivos no formato XLSX.

Criando uma planilha do Excel

Antes de escrever qualquer dado, precisamos entender a estrutura utilizada pelo Apache POI para representar uma planilha do Excel.

De forma simplificada, uma planilha possui a seguinte estrutura:

Workbook
 └── Sheet
      └── Row
            └── Cell

Cada elemento possui uma responsabilidade específica:

  • Workbook representa o arquivo Excel como um todo.
  • Sheet representa uma aba/planilha dentro do arquivo.
  • Row representa uma linha da planilha.
  • Cell representa uma célula dentro de uma linha.

Essa é a estrutura de objetos utilizada pelo Apache POI para representar os principais elementos de uma planilha. A API fornece classes e interfaces para trabalharmos com cada um desses elementos.

Vamos agora a um exemplo.

try(
    Workbook workbook = new XSSFWorkbook() ; 
    FileOutputStream output = new FileOutputStream("funcionarios.xlsx")  
){
        
    XSSFSheet sheet = (XSSFSheet) workbook.createSheet("Funcionários");

    Row row = sheet.createRow(0);
            
    row.createCell(0).setCellValue("Nome");
    row.createCell(1).setCellValue("Idade");
    row.createCell(2).setCellValue("Altura");
    row.createCell(3).setCellValue("Data Entrada");
    row.createCell(4).setCellValue("Ativo");
            
    Row row2 = sheet.createRow(1);
    row2.createCell(0).setCellValue("Rodrigo");
    row2.createCell(1).setCellValue(40);
    row2.createCell(2).setCellValue(1.80);
    row2.createCell(3).setCellValue(LocalDate.of(2010, 5, 24));
    row2.createCell(4).setCellValue(true);

    workbook.write(output);

}catch(Exception e){
    e.printStackTrace();
}

Nas primeiras linha utilizamos um try-with-resources para criar dois objetos: um XSSFWorkbook, que representa o arquivo Excel que estamos construindo em memória, e um FileOutputStream, que será utilizado para gravar o arquivo no disco.

O objeto Workbook representa o arquivo Excel como um todo e será utilizado para criar as planilhas que farão parte desse arquivo.

Na linha 6, utilizamos o método createSheet() para criar uma nova planilha, passando como parâmetro o seu nome. O método retorna um objeto Sheet, que representa a planilha/aba criada. Aqui fazemos o cast para XSSFSheet, que embora neste exemplo não seja necessário, para usos mais avançados será necessário.

A partir do objeto XSSFSheet, podemos criar as linhas que receberão os dados. Para isso, utilizamos o método createRow(), passando como parâmetro o número da linha que desejamos criar, começando no índice 0. O método retorna um objeto Row, que representa essa linha.

Utilizamos então o objeto Row para criar as células, adicionando os valores coluna por coluna. Para criar uma célula, utilizamos o método createCell(), passando como parâmetro o número da coluna.

É importante lembrar que o Apache POI utiliza índices numéricos iniciados em zero para identificar linhas e colunas. Dessa forma, a primeira coluna do Excel, que é identificada pela letra A, corresponde ao índice 0; a coluna B corresponde ao índice 1, e assim por diante.

O método createCell() retorna um objeto Cell, que representa a célula criada. A partir desse objeto podemos utilizar diversos métodos para configurar suas propriedades. Neste primeiro exemplo, vamos apenas definir o seu valor utilizando o método setCellValue().

O método setCellValue() possui várias versões sobrecarregadas e permite definir valores de diferentes tipos, como textos, números e valores booleanos.

Por fim, utilizamos o método write() do objeto Workbook, passando o OutputStream no qual desejamos gravar o arquivo. Nesse momento, o conteúdo que estava sendo mantido em memória pelo Workbook é escrito no arquivo funcionarios.xlsx.

Exemplo de arquivo básico do Excel criado em Java utilizando Apache POI

Ao abrir o arquivo gerado, podemos perceber que os dados foram gravados corretamente. No entanto, a data apresenta um problema de formatação: em vez de ser exibida como uma data, ela aparece como o número 40322.

Isso acontece porque o Excel armazena as datas internamente como números. O número representa uma determinada data e, quando a célula não possui um formato de data aplicado, o Excel exibe diretamente esse valor numérico. Se você já lidou com o Excel um pouco já deve ter visto este problema.

Para definir como os valores devem ser exibidos, o Apache POI disponibiliza recursos para criação e aplicação de formatos às células.

Formatando valores das células

A formatação de uma célula é aplicada por meio da classe CellStyle, que permite alterar diversas características da célula, como veremos mais adiante.

Neste momento, vamos nos concentrar na formatação dos valores. Para isso, utilizaremos a classe DataFormat para definir o formato que será aplicado ao CellStyle.

Vamos a um exemplo.

Sheet sheet = workbook.createSheet("Funcionários");
             
Row rowFormatada = sheet.createRow(0);
DataFormat dataFormat = workbook.createDataFormat();

CellStyle styleDinheiro = workbook.createCellStyle();
styleDinheiro.setDataFormat(dataFormat.getFormat("R$ #,##0.00"));
Cell dinheiro = rowFormatada.createCell(0);
dinheiro.setCellValue(2500);
dinheiro.setCellStyle(styleDinheiro);

CellStyle styleDecimal = workbook.createCellStyle();
styleDecimal.setDataFormat(dataFormat.getFormat("0.000"));
Cell decimal = rowFormatada.createCell(1);
decimal.setCellValue(12.5);
decimal.setCellStyle(styleDecimal);

CellStyle styleData = workbook.createCellStyle();
styleData.setDataFormat(dataFormat.getFormat("dd/MM/yyyy"));
Cell data = rowFormatada.createCell(2);
data.setCellValue(LocalDate.of(2010, 5, 24));
data.setCellStyle(styleData);

CellStyle stylePorcentagem = workbook.createCellStyle();
stylePorcentagem.setDataFormat(dataFormat.getFormat("0%"));
Cell porcentagem = rowFormatada.createCell(3);
porcentagem.setCellValue(0.5);
porcentagem.setCellStyle(stylePorcentagem);

workbook.write(output);

No exemplo acima, começamos criando o objeto DataFormat através do método createDataFormat() do Workbook. Utilizaremos esse objeto para obter os formatos que serão aplicados aos estilos das células.

Em seguida, criamos os objetos CellStyle através do método createCellStyle(), também disponibilizado pelo Workbook.

Para definir o formato do estilo, utilizamos o método setDataFormat() do CellStyle. Como parâmetro, passamos o formato obtido através do método getFormat() do DataFormat, informando a máscara desejada como uma String.

O formato utilizado pelo método getFormat() segue o mesmo padrão de códigos de formato personalizado utilizado pelo Microsoft Excel. Confira a documentação da Microsoft sobre formatos personalizados de números

Alterando o estilo de uma célula

Podemos alterar a aparência de uma célula utilizando um objeto CellStyle, da mesma forma que fizemos para formatar seus valores no exemplo anterior.

Cor de Fundo

Para definir uma cor de fundo para uma célula, utilizamos o método setFillForegroundColor() do CellStyle. Como parâmetro, podemos utilizar uma das cores disponibilizadas pelo enum IndexedColors, obtendo seu índice através do método getIndex().

Por exemplo, para utilizar a cor azul: style.setFillForegroundColor(IndexedColors.BLUE1.getIndex());

Além de definir a cor, também precisamos informar o tipo de preenchimento que será utilizado. Para um preenchimento sólido, utilizamos o método setFillPattern() com o valor FillPatternType.SOLID_FOREGROUND: style.setFillPattern(FillPatternType.SOLID_FOREGROUND); Dessa forma, a célula utilizará a cor definida como preenchimento de fundo.

CellStyle cabecalhoStyle = workbook.createCellStyle();
cabecalhoStyle.setFillForegroundColor(IndexedColors.BLUE1.getIndex());
cabecalhoStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);

Row cabecalho = sheet.createRow(0);
Cell hNome = cabecalho.createCell(0);
hNome.setCellValue("Nome");
hNome.setCellStyle(cabecalhoStyle);

Cell hDataEntrada = cabecalho.createCell(1);
hDataEntrada.setCellValue("Data Entrada");
hDataEntrada.setCellStyle(cabecalhoStyle);

Se desejarmos utilizar uma cor que não esteja disponível no enum IndexedColors, podemos definir uma cor personalizada utilizando seu código RGB.

Como estamos trabalhando com a API XSSF, podemos utilizar a classe XSSFColor. Ela permite criar uma cor a partir de um array de três valores, representando os componentes Red (vermelho), Green (verde) e Blue (azul).

Depois de criar a cor, podemos utilizá-la no CellStyle através do método setFillForegroundColor():

CellStyle cabecalhoStyle = workbook.createCellStyle();
byte[] rgb = new byte[]{(byte) 48, (byte) 121, (byte) 237};
XSSFColor meuAzul = new XSSFColor(rgb, null); 
cabecalhoStyle.setFillForegroundColor(meuAzul);
cabecalhoStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);

Fonte

Para alterar a fonte de uma célula, primeiro precisamos criar um objeto Font. Para isso, utilizamos o método createFont() do objeto Workbook.

Depois de criar a fonte, podemos configurar suas propriedades, como nome, tamanho, negrito, itálico e outras características.

Por fim, adicionamos a fonte ao CellStyle através do método setFont(). Dessa forma, o estilo da célula passa a utilizar as configurações definidas no objeto Font.

Font font  = workbook.createFont();
font .setBold(true);
font .setFontName("Courier New");
font .setColor(IndexedColors.WHITE.getIndex());
font .setFontHeightInPoints( (short) 18);
cabecalhoStyle.setFont(font );

Como podemos observar no exemplo, podemos configurar a fonte como negrito utilizando o método setBold(), passando true como argumento.

Para definir o tipo da fonte, utilizamos o método setFontName(), passando como argumento o nome da fonte que desejamos utilizar.

A cor da fonte pode ser definida através do método setColor(). Podemos passar como argumento o índice de uma das cores disponíveis no enum IndexedColors ou, quando estamos trabalhando com XSSF, um objeto XSSFColor, como fizemos anteriormente para definir uma cor de fundo personalizada.

Para definir o tamanho da fonte, utilizamos o método setFontHeightInPoints(), que recebe um valor do tipo short representando o tamanho da fonte em pontos.

Alinhamento

Para alterar o alinhamento horizontal de uma célula, utilizamos o método setAlignment(), passando como argumento um dos valores do enum HorizontalAlignment, como LEFT, CENTER, RIGHT ou JUSTIFY.

Por exemplo, para centralizar o conteúdo horizontalmente:

cabecalhoStyle.setAlignment(HorizontalAlignment.CENTER);

Para alterar o alinhamento vertical, utilizamos o método setVerticalAlignment(), passando como argumento um dos valores do enum VerticalAlignment, como TOP, CENTER ou BOTTOM.

Por exemplo:

cabecalhoStyle.setVerticalAlignment(VerticalAlignment.CENTER);

Tamanho de colunas e linhas

Para que os alinhamentos horizontal e vertical façam sentido, a célula precisa ter espaço suficiente para posicionar seu conteúdo. Por isso, também podemos alterar o tamanho das colunas e das linhas da planilha.

Para alterar a largura de uma coluna, utilizamos o objeto XSSFSheet e chamamos o método setColumnWidth(). O primeiro parâmetro representa o índice da coluna, começando em 0 (A → 0, B → 1 e assim por diante), enquanto o segundo define a largura da coluna.

Aqui temos uma pequena pegadinha: o Apache POI utiliza como unidade de medida 1/256 da largura do caractere 0.

sheet.setColumnWidth(0, 30 * 256);
sheet.setColumnWidth(1, 30 * 256);

No exemplo acima, definimos que as colunas A e B terão uma largura equivalente a aproximadamente 30 caracteres 0.

Sim, é um pouco estranho medir a largura dessa forma. Fazer o quê? 🙁

Também podemos utilizar o método autoSizeColumn(), que calcula automaticamente a largura da coluna de acordo com o conteúdo existente.

sheet.autoSizeColumn(0);
sheet.autoSizeColumn(1);

É importante lembrar que o método autoSizeColumn() deve ser chamado depois que os valores da planilha forem adicionados. Caso contrário, o Apache POI não terá todos os dados necessários para calcular corretamente a largura da coluna.

Para alterar a altura de uma linha, utilizamos o objeto Row correspondente e chamamos o método setHeightInPoints(), passando a altura desejada em pontos.

cabecalho.setHeightInPoints(60);

Dessa forma, podemos controlar tanto a largura das colunas quanto a altura das linhas para melhorar a apresentação dos dados na planilha.

Bordas

Para adicionar uma borda a uma célula, podemos utilizar os métodos setBorderTop(), setBorderRight(), setBorderBottom() e setBorderLeft() do CellStyle.

Esses métodos recebem como parâmetro um valor do enum BorderStyle, que define o estilo da borda. Entre os valores disponíveis estão NONE, THIN, MEDIUM, DASHED, DOTTED, THICK, DOUBLE, MEDIUM_DASHED e DASH_DOT, entre outros.

cabecalhoStyle.setBorderTop(BorderStyle.MEDIUM);
cabecalhoStyle.setBorderRight(BorderStyle.MEDIUM);
cabecalhoStyle.setBorderBottom(BorderStyle.MEDIUM);
cabecalhoStyle.setBorderLeft(BorderStyle.MEDIUM);

Também podemos definir a cor de cada borda utilizando os métodos setTopBorderColor(), setRightBorderColor(), setBottomBorderColor() e setLeftBorderColor(). Assim como vimos anteriormente, podemos utilizar uma cor do enum IndexedColors.

cabecalhoStyle.setTopBorderColor(IndexedColors.DARK_RED.getIndex());
cabecalhoStyle.setRightBorderColor(IndexedColors.DARK_RED.getIndex());
cabecalhoStyle.setBottomBorderColor(IndexedColors.DARK_RED.getIndex());
cabecalhoStyle.setLeftBorderColor(IndexedColors.DARK_RED.getIndex());

Não se esqueça que para colocar cor na borda primeiro devemos adicionar a borda.

Quando precisamos adicionar bordas a uma região que contém várias células, podemos utilizar a classe utilitária RegionUtil. Ela permite definir as bordas de um intervalo de células sem precisarmos configurar individualmente cada célula da região.

Para definir o intervalo, utilizamos a classe CellRangeAddress. Podemos criar um intervalo utilizando o método valueOf(), passando uma String no mesmo formato utilizado para identificar intervalos no Excel.

No exemplo abaixo, criamos um intervalo que vai da célula A1 até B3:

CellRangeAddress range = CellRangeAddress.valueOf("A1:B3");

Depois, podemos utilizar os métodos setBorderTop(), setBorderRight(), setBorderBottom() e setBorderLeft() do RegionUtil para definir as bordas da região:

RegionUtil.setBorderTop(BorderStyle.MEDIUM, range, sheet);
RegionUtil.setBorderRight(BorderStyle.MEDIUM, range, sheet);
RegionUtil.setBorderBottom(BorderStyle.MEDIUM, range, sheet);
RegionUtil.setBorderLeft(BorderStyle.MEDIUM, range, sheet);

Também podemos definir a cor de cada borda utilizando os métodos setTopBorderColor(), setRightBorderColor(), setBottomBorderColor() e setLeftBorderColor():

RegionUtil.setTopBorderColor(IndexedColors.BLUE1.getIndex(), range, sheet);
RegionUtil.setRightBorderColor(IndexedColors.BLUE1.getIndex(), range, sheet);
RegionUtil.setBottomBorderColor(IndexedColors.BLUE1.getIndex(), range, sheet);
RegionUtil.setLeftBorderColor(IndexedColors.BLUE1.getIndex(), range, sheet);

É importante observar que o RegionUtil trabalha sobre as células existentes na região. Por isso, devemos utilizá-lo depois que as células da região já tiverem sido criadas e os dados inseridos na planilha.

Fórmulas

Além de valores literais, também podemos inserir fórmulas em uma planilha. O procedimento é semelhante ao utilizado para adicionar um valor comum, mas, em vez de utilizar o método setCellValue(), utilizamos o método setCellFormula(), passando a fórmula como uma String.

A fórmula deve ser escrita seguindo a mesma sintaxe utilizada pelo Excel, porém sem o sinal de = no início.

Outro detalhe importante é que as funções devem ser informadas em inglês, independentemente do idioma utilizado na instalação do Excel. Por exemplo, para calcular a soma de um intervalo, utilizamos SUM(A1:A10).

Vamos a um exemplo:

Row cabecalho = sheet.createRow(0);
cabecalho.createCell(0).setCellValue("Valor 1");
cabecalho.createCell(1).setCellValue("Valor 2");
cabecalho.createCell(2).setCellValue("Produto");
            
Row l1 = sheet.createRow(1);
l1.createCell(0).setCellValue(10);
l1.createCell(1).setCellValue(20);
l1.createCell(2).setCellFormula("A2*B2");

Row l2 = sheet.createRow(2);
l2.createCell(0).setCellValue(50);
l2.createCell(1).setCellValue(35);
l2.createCell(2).setCellFormula("A3*B3");

Row l3 = sheet.createRow(3);
l3.createCell(0).setCellValue(15.5);
l3.createCell(1).setCellValue(1.5);
l3.createCell(2).setCellFormula("A4*B4");
            
Row total = sheet.createRow(4);
total.createCell(2).setCellFormula("SUM(C2:C4)");

Mesclando Células

Para mesclar células, utilizamos o método addMergedRegion() do objeto Sheet, passando como parâmetro um objeto CellRangeAddress que representa o intervalo que desejamos mesclar.

Quando utilizamos células mescladas para exibir um texto mais longo, podemos configurar a quebra automática de linha para que o conteúdo seja distribuído em várias linhas. Para isso, criamos um CellStyle e utilizamos o método setWrapText(true). Dessa forma, quando o texto ultrapassar o espaço disponível na região mesclada, o Excel poderá quebrá-lo automaticamente em várias linhas.

Row row = sheet.createRow(0);
            
row.createCell(0).setCellValue("Um texto bem grande para ter mais de uma coluna");
            
sheet.addMergedRegion(CellRangeAddress.valueOf("A1:C1"));
            
Row row2 = sheet.createRow(1);

CellStyle style = workbook.createCellStyle();
style.setWrapText(true);

Cell cell = row2.createCell(0);
cell.setCellValue("Um texto bem grande para ter mais de uma linha");
cell.setCellStyle(style);

sheet.addMergedRegion( CellRangeAddress.valueOf("A2:C6") );

Criando Tabelas com Filtro no Apache POI

Um recurso bastante útil do Excel são as tabelas, que, além de facilitar a organização dos dados, permitem utilizar recursos como filtros e ordenação diretamente no cabeçalho.

Para criar uma tabela utilizando o Apache POI, podemos utilizar o método createTable() do XSSFSheet. Esse método recebe um objeto AreaReference, que representa o intervalo de células que fará parte da tabela, e retorna um objeto XSSFTable.

A partir do objeto XSSFTable, podemos configurar algumas características da tabela, como seu nome e seu estilo.

Para definir o nome da tabela, utilizamos o método setName(). Já para definir o estilo visual da tabela, utilizamos o método setStyleName(). O Apache POI permite utilizar os estilos de tabela disponibilizados pelo Excel, como: "TableStyleLight1" "TableStyleLight2", "TableStyleMedium2", "TableStyleMedium9", "TableStyleMedium10", "TableStyleDark2"ou "TableStyleDark9".

Por fim, para adicionar os filtros ao cabeçalho da tabela, podemos utilizar o método getCTTable().addNewAutoFilter():

XSSFTable table = sheet.createTable(
    new AreaReference("A1:E2", null)
);
table.setName("Funcionarios");

table.setStyleName("TableStyleMedium2");

table.getCTTable().addNewAutoFilter();

Criando Gráficos

Agora vamos ver como criar um gráfico simples utilizando o Apache POI.

Neste exemplo, vamos criar uma planilha contendo alguns produtos e suas respectivas quantidades vendidas. Em seguida, utilizaremos esses dados para criar um gráfico de colunas, permitindo visualizar de forma mais clara a quantidade vendida de cada produto.

Sheet sheet = workbook.createSheet("Vendas"); 

Row cabecalho = sheet.createRow(0);
cabecalho.createCell(0).setCellValue("Produto");
cabecalho.createCell(1).setCellValue("Quantidade");

Row row1 = sheet.createRow(1);
row1.createCell(0).setCellValue("Notebook");
row1.createCell(1).setCellValue(25);

Row row2 = sheet.createRow(2);
row2.createCell(0).setCellValue("Monitor");
row2.createCell(1).setCellValue(18);

 Row row3 = sheet.createRow(3);
row3.createCell(0).setCellValue("Teclado");
row3.createCell(1).setCellValue(32);

Row row4 = sheet.createRow(4);
row4.createCell(0).setCellValue("Mouse");
row4.createCell(1).setCellValue(45);

Com os dados da planilha criados, podemos começar a trabalhar com o gráfico.

No Apache POI, os gráficos são inseridos em uma área de desenho (drawing) da planilha. Para criar essa área, utilizamos o método createDrawingPatriarch() do objeto Sheet.

O método retorna um objeto XSSFDrawing, que utilizaremos posteriormente para definir a posição e o tamanho do gráfico dentro da planilha.

XSSFDrawing drawing = sheet.createDrawingPatriarch();

A partir desse objeto, podemos criar o gráfico e definir sua posição na planilha.

Agora precisamos informar ao Apache POI onde o gráfico será colocado e qual será o seu tamanho dentro da planilha.

Para isso, utilizamos a classe XSSFClientAnchor, que permite definir as células que serão utilizadas como referência para a posição e as dimensões do gráfico.

XSSFClientAnchor anchor = new XSSFClientAnchor(
    0, 0, 0, 0,
    0, 5, 10, 25
);

O XSSFClientAnchor funciona como uma espécie de âncora que define a posição e as dimensões do gráfico dentro da planilha.

Os quatro primeiros valores (0, 0, 0, 0) representam deslocamentos internos dentro das células. Neste exemplo, não iremos alterar esses valores, portanto, o gráfico não terá nenhum deslocamento adicional dentro das células.

Os quatro valores seguintes (0, 5, 10, 25) definem a posição e o tamanho do gráfico utilizando as células da planilha.

Nesse caso, o gráfico começa na coluna 0, que corresponde à coluna A, e na linha 5, que corresponde à linha 6. Ele termina na coluna 10, que corresponde à coluna K, e na linha 25, que corresponde à linha 26.

Com a âncora criada, podemos finalmente criar o gráfico:

XSSFChart chart = drawing.createChart(anchor);

O método createChart() recebe a âncora criada anteriormente e cria um novo gráfico dentro da região definida por ela.

Agora precisamos configurar os eixos do gráfico. Neste exemplo, criaremos um gráfico de colunas, no qual o eixo horizontal representará os nomes dos produtos, enquanto o eixo vertical representará as quantidades vendidas.

Para representar os nomes dos produtos, criamos um eixo de categorias utilizando o método createCategoryAxis() do objeto XSSFChart.

XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
bottomAxis.setTitle("Produto");

Já para representar as quantidades vendidas, precisamos criar o eixo de valores. Para isso, utilizamos o método createValueAxis() do objeto XSSFChart:

XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
leftAxis.setTitle("Quantidade");

Agora precisamos informar ao gráfico quais células da planilha serão utilizadas como fonte de dados. Para isso, o Apache POI disponibiliza a classe XDDFDataSourcesFactory, que possui métodos para criar fontes de dados a partir de intervalos de células.

Primeiro, vamos criar a fonte de dados que será utilizada pelo eixo de categorias. Como as categorias são os nomes dos produtos, precisamos utilizar o método fromStringCellRange().

XDDFDataSource<String> xs = XDDFDataSourcesFactory.fromStringCellRange(
    ((XSSFSheet) sheet), 
    new CellRangeAddress(1, 4, 0, 0) // A2:A5 - Veja imagem
);

Do mesmo modo, precisamos criar a fonte de dados para o eixo de valores. Nesse caso, como estamos trabalhando com valores numéricos, utilizamos o método fromNumericCellRange() da classe XDDFDataSourcesFactory.

XDDFNumericalDataSource<Double> ys = XDDFDataSourcesFactory.fromNumericCellRange(
    ((XSSFSheet) sheet), 
    new CellRangeAddress(1, 4, 1, 1)  // B2:B5  Veja imagem
);

Agora que os eixos e as fontes de dados estão preparados, podemos criar e configurar o tipo de gráfico que será utilizado.

Para isso, utilizamos o método createData() do objeto XSSFChart, informando o tipo de gráfico e os eixos que criamos anteriormente.

XDDFBarChartData data = (XDDFBarChartData) chart.createData(
    ChartTypes.BAR, 
    bottomAxis, 
    leftAxis
);
data.setBarDirection(BarDirection.COL);

O método createData() cria a estrutura de dados que será utilizada pelo gráfico. Para isso, passamos o tipo de gráfico, nesse caso ChartTypes.BAR, e os dois eixos que criamos anteriormente.

Por padrão, o tipo BAR utiliza barras na orientação horizontal. Como queremos criar um gráfico de colunas, utilizamos o método setBarDirection() para alterar a orientação para vertical, passando BarDirection.COL.

O Apache POI também permite criar outros tipos de gráficos utilizando uma estrutura semelhante. Por exemplo, para criar um gráfico de linhas, podemos utilizar ChartTypes.LINE e trabalhar com um objeto XDDFLineChartData

Agora podemos adicionar os dados ao gráfico:

XDDFBarChartData.Series series1 = (XDDFBarChartData.Series) data.addSeries(xs, ys);
series1.setTitle("Quantidade", null);

Agora que o tipo de gráfico e seus eixos estão configurados, podemos adicionar as fontes de dados que criamos anteriormente e formar a série que será exibida no gráfico.

chart.plot(data);

Lendo um arquivo do Excel

Para abrir e realizar a leitura de um arquivo do Excel, podemos utilizar novamente a classe XSSFWorkbook, que já conhecemos. A diferença é que passamos um InputStream contendo o arquivo que desejamos ler.

Podemos fazer isso dentro de um try-with-resources, garantindo que tanto o fluxo de entrada quanto o Workbook sejam fechados automaticamente ao final da operação.

Vamos a um exemplo:

try(
    FileInputStream input = new FileInputStream("dados.xlsx")  ;
    Workbook workbook = new XSSFWorkbook(input); 
){
    // ...  
}catch(Exception e){
    e.printStackTrace();
}

A partir do objeto Workbook, podemos acessar as planilhas do arquivo. Lembrando da estrutura que vimos no início do artigo:

Workbook
 └── Sheet
      └── Row
           └── Cell

Para obter uma planilha, podemos utilizar o método getSheetAt(), passando como parâmetro o índice da planilha dentro do Workbook. Também podemos utilizar o método getSheet(), passando o nome da planilha:

Sheet sheet = workbook.getSheetAt(0);

Sheet sheet2 = workbook.getSheet("Funcionários");

Assim como acontece com linhas e colunas, o índice das planilhas começa em 0. Portanto, getSheetAt(0) retorna a primeira planilha do arquivo.

Depois de obter o objeto Sheet, podemos acessar uma linha utilizando o método getRow(), passando o índice da linha. Esse índice também começa em 0, portanto, a primeira linha da planilha corresponde ao índice 0, a segunda ao índice 1 e assim por diante.

Row row = sheet.getRow(0);

Com um objeto Row, podemos acessar uma célula utilizando o método getCell(), passando o índice da coluna. Esse índice também começa em 0, portanto, a coluna A corresponde ao índice 0, a coluna B ao índice 1 e assim por diante.

Cell cell = row.getCell(0);

O objeto Cell possui métodos específicos para obter seu conteúdo de acordo com o tipo de dado armazenado. Alguns dos principais métodos são:

  • getStringCellValue(): retorna o conteúdo da célula como String.
  • getNumericCellValue(): retorna o conteúdo numérico como double.
  • getDateCellValue(): retorna o conteúdo como um objeto Date.
  • getBooleanCellValue(): retorna o conteúdo como boolean.
  • getCellFormula(): retorna a fórmula armazenada na célula como String.

É importante observar que devemos utilizar o método adequado ao tipo de conteúdo da célula. Caso tentemos obter um valor utilizando um método incompatível com o tipo armazenado, poderemos obter uma exceção durante a execução.

Veja agora um exemplo de leitura da seguinte planilha:

XSSFSheet sheet = (XSSFSheet) workbook.getSheetAt(0);
// ou XSSFSheet sheet = (XSSFSheet) workbook.getSheet("Funcinários");

for(int i = 1; i <= sheet.getLastRowNum(); i++){

    Row row = sheet.getRow(i);

    System.out.print(row.getCell(0).getStringCellValue() + "\t");
    System.out.print(row.getCell(1).getNumericCellValue() + "\t");
    System.out.print(row.getCell(2).getNumericCellValue() + "\t");

    Date d = row.getCell(3).getDateCellValue();
    LocalDate date = Instant.ofEpochMilli(d.getTime()).atZone(ZoneId.systemDefault()).toLocalDate();
    System.out.print( date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")) + "\t");

    System.out.print(row.getCell(4).getBooleanCellValue() +"\t");
    System.out.println(row.getCell(5).getCellFormula());
}

// Saida:
// Rodrigo 1985.0  1.8     24/05/2010      true    YEAR(TODAY())-B2
// Antônio 2000.0  1.75    03/12/2020      false   YEAR(TODAY())-B3
// Ângela  1998.0  1.58    05/08/2020      true    YEAR(TODAY())-B4

Caso seja necessário descobrir qual tipo de valor está presente em uma célula, podemos utilizar o método getCellType(). Esse método retorna um valor do enum CellType, que permite identificar o tipo de conteúdo armazenado na célula.

Por exemplo:

CellType tipo = cell.getCellType();

O enum CellType possui valores que representam os principais tipos de conteúdo que podemos encontrar em uma célula, como:

  • STRING: texto.
  • NUMERIC: valor numérico ou data.
  • BOOLEAN: valor booleano (true ou false).
  • FORMULA: célula que contém uma fórmula.
  • BLANK: célula vazia.
  • ERROR: célula contendo um valor de erro.

Isso é bastante útil quando precisamos percorrer uma planilha sem conhecer previamente o tipo de dado armazenado em cada célula. Podemos verificar o tipo retornado e, a partir dele, utilizar o método adequado para obter seu conteúdo.

Row row = sheet.getRow(1);

for(Cell cell : row){
    if(cell.getCellType() == CellType.STRING){
        System.out.print(cell.getStringCellValue() + " é uma String");
     }
}

Bom, esta foi uma introdução ao Apache POI, uma biblioteca bastante útil para trabalhar com arquivos do Microsoft Excel em aplicações Java.

Ao longo do artigo, vimos como criar arquivos no formato XLSX, adicionar planilhas, linhas e células, definir diferentes tipos de valores e aplicar formatações. Também vimos como trabalhar com estilos, fontes, alinhamentos, bordas, fórmulas, células mescladas e tabelas com filtros.

Além da criação de planilhas, aprendemos como abrir arquivos existentes e realizar a leitura de seus dados, identificando o tipo de cada célula e utilizando os métodos apropriados para obter seus valores.

Esses recursos são bastante úteis no desenvolvimento de sistemas, principalmente em aplicações que precisam gerar relatórios, exportar informações para o Excel ou importar dados fornecidos pelos usuários.

O Apache POI possui ainda diversos outros recursos que não foram abordados neste artigo, mas os conceitos apresentados aqui já fornecem uma boa base para começar a trabalhar com arquivos Excel em projetos Java.

T++