CI para projetos Java com Maven usando GitHub Actions
1. Visão geral
Continuous Integration (CI) ajuda você a detectar erros logo quando faz push do código, em vez de esperar até o merge e tudo quebrar. Este guia mostra como montar um pipeline CI para seu projeto Java com Maven no GitHub Actions: build, execução de testes, cache de dependências, matriz de build com múltiplas versões do JDK e publicação de relatórios. Ao final, você poderá copiar o arquivo de workflow para seu projeto real e executá-lo em poucos minutos.
2. Preparando o projeto de exemplo
Suponha que você tenha um projeto Spring Boot simples com estrutura Maven padrão. Um arquivo pom.xml mínimo seria assim:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.nonladev</groupId>
<artifactId>order-service</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.25.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins>
</build>
</project>
A dependência junit-jupiter e assertj-core são usadas para escrever testes. O plugin maven-surefire-plugin é responsável por executar os testes quando você executa mvn test.
Uma classe de lógica de negócio pequena para testar:
package com.nonladev.order;
public class OrderCalculator {
public double calculateTotal(double unitPrice, int quantity) {
if (quantity <= 0) {
throw new IllegalArgumentException("Quantity deve ser maior que 0");
}
return unitPrice * quantity;
}
}
E o teste correspondente:
package com.nonladev.order;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class OrderCalculatorTest {
private final OrderCalculator calculator = new OrderCalculator();
@Test
void shouldCalculateTotalCorrectly() {
double total = calculator.calculateTotal(50_000, 3);
assertThat(total).isEqualTo(150_000);
}
@Test
void shouldThrowWhenQuantityIsInvalid() {
assertThatThrownBy(() -> calculator.calculateTotal(50_000, 0))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Quantity deve ser maior que 0");
}
}
shouldCalculateTotalCorrectly verifica se o cálculo do total está correto. shouldThrowWhenQuantityIsInvalid verifica se uma exceção é lançada quando quantity é inválida. Estes são os testes que o CI executará cada vez que você fizer push do código.
3. Workflow CI básico
GitHub Actions lê arquivos YAML no diretório .github/workflows/. Crie o arquivo .github/workflows/ci.yml:
name: CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Build and run tests
run: mvn -B clean verify
Explicação de cada parte:
on: o workflow é executado quando há umpushoupull_requestdirecionado para a branchmain.actions/checkout@v4: clona o código-fonte para o runner.actions/setup-java@v4: instala o JDK 21 da distribuição Temurin — você sempre precisa dessa etapa antes de executarmvn.mvn -B clean verify: a flag-B(batch mode) desativa a saída interativa, adequada para ambiente CI.clean verifyreconstrói do zero e executa os testes (a faseverifyincluitest).
Faça push do commit, vá até a aba Actions no GitHub, e você verá o job rodando com um log similar a:
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.nonladev.order.OrderCalculatorTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS
Se o teste falhar, o job terá status vermelho e você verá o stack trace direto no log — sem precisar de SSH em nenhuma máquina.
4. Cache de dependências Maven para builds mais rápidos
Cada vez que o job é executado em um novo runner (efêmero), Maven precisa baixar todas as dependências novamente de .m2/repository. Para um projeto Spring Boot, isso pode levar 1–2 minutos cada vez. setup-java oferece suporte integrado a cache:
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: 'maven'
A propriedade cache: 'maven' automaticamente cacheia o diretório ~/.m2/repository com base no hash do arquivo pom.xml. No próximo build, se o pom.xml não mudar, o Actions restaura o cache imediatamente, pulando o download de cada jar.
Você pode verificar a eficácia observando o tempo da etapa “Build and run tests” antes e depois de adicionar o cache — geralmente reduz 30–60%.
Nota: Se seu projeto usa Maven Wrapper (
./mvnw), é melhor cachear de acordo commvnwem vez de confiar na versão Maven pré-instalada no runner, pois cada vez que GitHub atualiza a imagem, a versão padrão do Maven pode mudar.
5. Matriz de build com múltiplas versões do JDK
Se você mantém uma biblioteca que precisa ser compatível com múltiplas versões do Java, use strategy.matrix para executar vários jobs em paralelo:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
java-version: [ '17', '21' ]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java-version }}
distribution: 'temurin'
cache: 'maven'
- name: Build and run tests
run: mvn -B clean verify
matrix.java-version lista as versões que você deseja testar. O GitHub Actions cria automaticamente 2 jobs executando em paralelo: um com JDK 17 e outro com JDK 21. A variável ${{ matrix.java-version }} é substituída em cada job correspondente.
Os resultados na aba Actions mostram 2 jobs separados, por exemplo build (17) e build (21), ajudando você a saber imediatamente em qual versão Java o projeto falha.
6. Publicar relatório de testes e verificar cobertura
Por padrão, o log dos testes é exibido apenas como texto no Actions. Para ter um relatório mais fácil de ler, use a action dorny/test-reporter:
- name: Build and run tests
run: mvn -B clean verify
- name: Publish test report
uses: dorny/test-reporter@v1
if: success() || failure()
with:
name: Maven Tests
path: '**/surefire-reports/*.xml'
reporter: java-junit
if: success() || failure(): essa etapa é executada mesmo quando o teste falha (mas não é executada se o job for cancelado), para que você sempre veja o relatório. path aponta para o arquivo XML que o Surefire gera após executar os testes. O relatório aparece como uma verificação separada no PR, listando cada caso de teste como pass/fail.
Se você quer medir a cobertura, adicione o plugin JaCoCo ao pom.xml:
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
Executar mvn verify irá gerar automaticamente um relatório HTML em target/site/jacoco/index.html. Você pode fazer upload dele como um artefato:
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: jacoco-report
path: target/site/jacoco/
Este artefato pode ser baixado da página de resumo da execução do workflow e visualizado diretamente no navegador.
7. Badge de status do build
Depois que o workflow está rodando estável, adicione um badge ao README.md para que as pessoas saibam o status do CI sem precisar entrar no Actions:

Substitua <org>/<repo> pelo seu nome real do GitHub. O badge se atualiza automaticamente de verde/vermelho conforme o resultado da execução mais recente na branch padrão.
8. Erros comuns
- Esquecer de definir
java-versioncorrespondendo aomaven.compiler.source: o build local passa, mas o CI falha com o errorelease version X not supported. Sempre sincronize a versão do JDK entrepom.xmle workflow. - Usar
mvn testem vez demvn verify: se seu projeto tem testes de integração que rodam na faseverify(por exemplo, plugin Failsafe),mvn testvai pular eles, criando uma falsa sensação de segurança. - Cache com chave errada ao mudar dependências: se você escrever manualmente a action de cache (
actions/cache) e esquecer de incluirpom.xmlna chave hash, o cache antigo pode fazer o build usar dependências desatualizadas. Usecache: 'maven'dosetup-javapara evitar isso. - Executar CI em um runner self-hosted sem Docker/rede estável: Maven precisa baixar dependências do repositório central; se o runner estiver bloqueado de rede, o build vai travar na etapa de download. Verifique o proxy ou use um mirror Maven interno.
Nota: Se seu projeto é muito pequeno, tem apenas alguns testes simples e não faz deploy frequentemente, um workflow CI completo com matriz + relatório de cobertura pode ser exagerado. Comece com o workflow básico na seção 3 e depois expanda gradualmente à medida que o projeto cresce.
9. Conclusão
Com apenas algumas dezenas de linhas YAML, você tem um pipeline CI que automaticamente faz build, executa testes, cacheia dependências e publica relatórios para seu projeto Maven. Comece com um workflow simples, depois adicione matriz JDK, cobertura JaCoCo e badge README conforme necessário. O mais importante não é ter um workflow complexo, mas sim ter um CI confiável e estável que roda cada vez que você faz push do código.