GitHub Packages is a powerful package registry that allows you to securely publish and consume packages within your organization or publicly. When combined with Maven, it provides a seamless experience for managing Java dependencies directly from your GitHub repositories.
What is GitHub Packages?
GitHub Packages is a package hosting service that enables you to:
- Host Maven packages alongside your source code
- Control access using GitHub permissions
- Integrate with CI/CD workflows
- Manage dependencies across multiple projects
- Leverage GitHub's security features
Architecture Overview
[Local Maven] → [GitHub Packages Registry] ← [GitHub Actions CI/CD] | | | Publish artifacts Store packages Automated publishing Pull dependencies Manage versions and dependency updates
Hands-On Tutorial: Complete GitHub Packages Maven Setup
Let's build a complete multi-module Java project that publishes libraries to GitHub Packages and consumes them from other projects.
Step 1: Project Structure Setup
github-packages-demo/ ├── libraries/ │ ├── core-utils/ │ ├── security-library/ │ └── data-access/ ├── applications/ │ ├── api-service/ │ └── batch-processor/ ├── .github/ │ └── workflows/ ├── .m2/ │ └── settings.xml ├── pom.xml └── README.md
Step 2: Parent POM Configuration
pom.xml (Parent):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.yourusername</groupId>
<artifactId>github-packages-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>GitHub Packages Demo</name>
<description>Demo project for GitHub Packages with Maven</description>
<url>https://github.com/yourusername/github-packages-demo</url>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Dependency Versions -->
<spring-boot.version>3.2.0</spring-boot.version>
<jackson.version>2.16.1</jackson.version>
<junit.version>5.10.1</junit.version>
<lombok.version>1.18.30</lombok.version>
<!-- GitHub Packages Configuration -->
<github.packages.url>https://maven.pkg.github.com/yourusername</github.packages.url>
<github.repository>yourusername/github-packages-demo</github.repository>
</properties>
<modules>
<module>libraries/core-utils</module>
<module>libraries/security-library</module>
<module>libraries/data-access</module>
<module>applications/api-service</module>
<module>applications/batch-processor</module>
</modules>
<distributionManagement>
<repository>
<id>github</id>
<name>GitHub Packages</name>
<url>${github.packages.url}/${github.repository}</url>
</repository>
<snapshotRepository>
<id>github</id>
<name>GitHub Packages</name>
<url>${github.packages.url}/${github.repository}</url>
</snapshotRepository>
</distributionManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
<configuration>
<source>17</source>
<detectJavaApiLink>false</detectJavaApiLink>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>github</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<altDeploymentRepository>
github::default::${github.packages.url}/${github.repository}
</altDeploymentRepository>
</properties>
</profile>
</profiles>
</project>
Step 3: Maven Settings Configuration
.m2/settings.xml:
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<!-- GitHub Packages Server Configuration -->
<server>
<id>github</id>
<username>your-github-username</username>
<!-- Use Personal Access Token (PAT) as password -->
<password>${env.GITHUB_TOKEN}</password>
</server>
</servers>
<profiles>
<profile>
<id>github</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<!-- GitHub Packages Repository -->
<repository>
<id>github</id>
<name>GitHub Packages</name>
<url>https://maven.pkg.github.com/yourusername/*</url>
<releases>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
</repositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>github</activeProfile>
</activeProfiles>
</settings>
Step 4: Core Utilities Library
libraries/core-utils/pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.yourusername</groupId>
<artifactId>github-packages-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>core-utils</artifactId>
<packaging>jar</packaging>
<name>Core Utilities Library</name>
<description>Common utilities and helper classes</description>
<dependencies>
<!-- Jackson for JSON processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
libraries/core-utils/src/main/java/com/github/yourusername/coreutils/JsonUtils.java:
package com.github.yourusername.coreutils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.experimental.UtilityClass;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
/**
* Utility class for JSON operations
*/
@UtilityClass
public class JsonUtils {
private static final ObjectMapper OBJECT_MAPPER = createObjectMapper();
private static ObjectMapper createObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
return mapper;
}
/**
* Convert object to JSON string
*/
public static String toJson(Object object) {
try {
return OBJECT_MAPPER.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new JsonSerializationException("Failed to serialize object to JSON", e);
}
}
/**
* Convert JSON string to object
*/
public static <T> T fromJson(String json, Class<T> clazz) {
try {
return OBJECT_MAPPER.readValue(json, clazz);
} catch (JsonProcessingException e) {
throw new JsonDeserializationException("Failed to deserialize JSON: " + json, e);
}
}
/**
* Convert JSON string to typed object
*/
public static <T> T fromJson(String json, TypeReference<T> typeReference) {
try {
return OBJECT_MAPPER.readValue(json, typeReference);
} catch (JsonProcessingException e) {
throw new JsonDeserializationException("Failed to deserialize JSON", e);
}
}
/**
* Convert JSON input stream to object
*/
public static <T> T fromJson(InputStream inputStream, Class<T> clazz) {
try {
return OBJECT_MAPPER.readValue(inputStream, clazz);
} catch (IOException e) {
throw new JsonDeserializationException("Failed to deserialize JSON from stream", e);
}
}
/**
* Convert object to pretty-printed JSON
*/
public static String toPrettyJson(Object object) {
try {
return OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new JsonSerializationException("Failed to serialize object to pretty JSON", e);
}
}
/**
* Convert object to Map
*/
public static Map<String, Object> toMap(Object object) {
return OBJECT_MAPPER.convertValue(object, new TypeReference<Map<String, Object>>() {});
}
/**
* Convert Map to object
*/
public static <T> T fromMap(Map<String, Object> map, Class<T> clazz) {
return OBJECT_MAPPER.convertValue(map, clazz);
}
// Custom exceptions
public static class JsonSerializationException extends RuntimeException {
public JsonSerializationException(String message, Throwable cause) {
super(message, cause);
}
}
public static class JsonDeserializationException extends RuntimeException {
public JsonDeserializationException(String message) {
super(message);
}
public JsonDeserializationException(String message, Throwable cause) {
super(message, cause);
}
}
}
libraries/core-utils/src/main/java/com/github/yourusername/coreutils/DateUtils.java:
package com.github.yourusername.coreutils;
import lombok.experimental.UtilityClass;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
/**
* Utility class for date operations
*/
@UtilityClass
public class DateUtils {
public static final String ISO_DATE_FORMAT = "yyyy-MM-dd";
public static final String ISO_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
/**
* Format LocalDate to ISO string
*/
public static String formatIsoDate(LocalDate date) {
return date.format(DateTimeFormatter.ISO_DATE);
}
/**
* Format LocalDateTime to ISO string
*/
public static String formatIsoDateTime(LocalDateTime dateTime) {
return dateTime.format(DateTimeFormatter.ISO_DATE_TIME);
}
/**
* Parse ISO date string to LocalDate
*/
public static LocalDate parseIsoDate(String dateString) {
return LocalDate.parse(dateString, DateTimeFormatter.ISO_DATE);
}
/**
* Calculate days between two dates
*/
public static long daysBetween(LocalDate start, LocalDate end) {
return ChronoUnit.DAYS.between(start, end);
}
/**
* Check if date is within range
*/
public static boolean isWithinRange(LocalDate date, LocalDate start, LocalDate end) {
return !date.isBefore(start) && !date.isAfter(end);
}
/**
* Get current timestamp as string
*/
public static String currentTimestamp() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
}
}
Step 5: Security Library
libraries/security-library/pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.yourusername</groupId>
<artifactId>github-packages-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>security-library</artifactId>
<packaging>jar</packaging>
<name>Security Library</name>
<description>Security utilities and JWT handling</description>
<dependencies>
<!-- Internal dependency -->
<dependency>
<groupId>com.github.yourusername</groupId>
<artifactId>core-utils</artifactId>
<version>${project.version}</version>
</dependency>
<!-- JWT Libraries -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<!-- Password Hashing -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
<version>6.2.0</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
libraries/security-library/src/main/java/com/github/yourusername/security/JwtTokenProvider.java:
package com.github.yourusername.security;
import com.github.yourusername.coreutils.JsonUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.crypto.SecretKey;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* JWT Token provider for authentication
*/
@Slf4j
public class JwtTokenProvider {
private final SecretKey secretKey;
private final long expirationMs;
private final PasswordEncoder passwordEncoder;
public JwtTokenProvider(String secret, long expirationMs) {
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes());
this.expirationMs = expirationMs;
this.passwordEncoder = new BCryptPasswordEncoder();
}
/**
* Generate JWT token for user
*/
public String generateToken(String username, Map<String, Object> claims) {
Instant now = Instant.now();
Instant expiration = now.plus(expirationMs, ChronoUnit.MILLIS);
Map<String, Object> actualClaims = new HashMap<>();
if (claims != null) {
actualClaims.putAll(claims);
}
actualClaims.put("username", username);
return Jwts.builder()
.claims(actualClaims)
.subject(username)
.issuedAt(Date.from(now))
.expiration(Date.from(expiration))
.signWith(secretKey)
.compact();
}
/**
* Generate JWT token with basic claims
*/
public String generateToken(String username) {
return generateToken(username, Map.of());
}
/**
* Validate JWT token
*/
public boolean validateToken(String token) {
try {
Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token);
return true;
} catch (Exception e) {
log.warn("Invalid JWT token: {}", e.getMessage());
return false;
}
}
/**
* Extract username from token
*/
public String getUsernameFromToken(String token) {
Claims claims = getAllClaimsFromToken(token);
return claims.getSubject();
}
/**
* Extract all claims from token
*/
public Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload();
}
/**
* Extract custom claim from token
*/
public <T> T getClaimFromToken(String token, String claimName, Class<T> clazz) {
Claims claims = getAllClaimsFromToken(token);
return claims.get(claimName, clazz);
}
/**
* Hash password using BCrypt
*/
public String hashPassword(String rawPassword) {
return passwordEncoder.encode(rawPassword);
}
/**
* Verify password against hash
*/
public boolean verifyPassword(String rawPassword, String hashedPassword) {
return passwordEncoder.matches(rawPassword, hashedPassword);
}
/**
* Check if token is expired
*/
public boolean isTokenExpired(String token) {
Claims claims = getAllClaimsFromToken(token);
return claims.getExpiration().before(new Date());
}
}
Step 6: Data Access Library
libraries/data-access/pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.yourusername</groupId>
<artifactId>github-packages-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>data-access</artifactId>
<packaging>jar</packaging>
<name>Data Access Library</name>
<description>Database access and repository patterns</description>
<dependencies>
<!-- Internal dependencies -->
<dependency>
<groupId>com.github.yourusername</groupId>
<artifactId>core-utils</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Data -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>3.2.0</version>
</dependency>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.4.1.Final</version>
</dependency>
<!-- Connection Pool -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.1.0</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Step 7: API Service Application
applications/api-service/pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.yourusername</groupId>
<artifactId>github-packages-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>api-service</artifactId>
<packaging>jar</packaging>
<name>API Service</name>
<description>REST API Service using internal libraries</description>
<dependencies>
<!-- Internal libraries from GitHub Packages -->
<dependency>
<groupId>com.github.yourusername</groupId>
<artifactId>core-utils</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.github.yourusername</groupId>
<artifactId>security-library</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.github.yourusername</groupId>
<artifactId>data-access</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- Database -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.1</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
applications/api-service/src/main/java/com/github/yourusername/api/ApiServiceApplication.java:
package com.github.yourusername.api;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ApiServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ApiServiceApplication.class, args);
}
}
applications/api-service/src/main/java/com/github/yourusername/api/controller/UserController.java:
package com.github.yourusername.api.controller;
import com.github.yourusername.coreutils.JsonUtils;
import com.github.yourusername.security.JwtTokenProvider;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final JwtTokenProvider jwtTokenProvider;
@PostMapping("/login")
public ResponseEntity<Map<String, String>> login(@RequestBody LoginRequest request) {
log.info("Login attempt for user: {}", request.username());
// In real application, validate credentials against database
String token = jwtTokenProvider.generateToken(request.username());
return ResponseEntity.ok(Map.of(
"token", token,
"username", request.username(),
"message", "Login successful"
));
}
@GetMapping("/profile")
public ResponseEntity<Map<String, Object>> getProfile(@RequestHeader("Authorization") String authHeader) {
String token = authHeader.replace("Bearer ", "");
if (!jwtTokenProvider.validateToken(token)) {
return ResponseEntity.status(401).body(Map.of("error", "Invalid token"));
}
String username = jwtTokenProvider.getUsernameFromToken(token);
Map<String, Object> claims = jwtTokenProvider.getAllClaimsFromToken(token);
return ResponseEntity.ok(Map.of(
"username", username,
"claims", claims,
"tokenInfo", JsonUtils.toMap(claims)
));
}
public record LoginRequest(String username, String password) {}
}
Step 8: GitHub Actions CI/CD Configuration
.github/workflows/maven-publish.yml:
name: Publish to GitHub Packages on: push: branches: [ main, develop ] tags: [ 'v*' ] pull_request: branches: [ main ] jobs: build-and-publish: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up JDK 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' cache: 'maven' - name: Build and test run: mvn -B clean verify - name: Configure Git run: | git config user.name "GitHub Actions" git config user.email "[email protected]" - name: Publish to GitHub Packages if: github.event_name != 'pull_request' run: mvn -B deploy env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create GitHub Release if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v1 with: files: | **/target/*.jar **/target/*-sources.jar **/target/*-javadoc.jar dependency-update: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up JDK 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' - name: Check for dependency updates run: mvn versions:display-dependency-updates versions:display-plugin-updates
.github/workflows/dependency-review.yml:
name: Dependency Review on: pull_request: branches: [ main ] permissions: contents: read jobs: dependency-review: runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Dependency Review uses: actions/dependency-review-action@v3
Step 9: Publishing to GitHub Packages
Manual Publishing Commands:
# Set GitHub Token as environment variable export GITHUB_TOKEN=ghp_your_personal_access_token # Deploy all modules mvn clean deploy # Deploy specific module mvn clean deploy -pl libraries/core-utils # Skip tests during deployment mvn clean deploy -DskipTests # Deploy with specific profile mvn clean deploy -P github
Step 10: Consuming from Other Projects
External Project pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>external-consumer</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <repositories> <repository> <id>github</id> <name>GitHub Packages</name> <url>https://maven.pkg.github.com/yourusername/github-packages-demo</url> </repository> </repositories> <dependencies> <!-- Consuming libraries from GitHub Packages --> <dependency> <groupId>com.github.yourusername</groupId> <artifactId>core-utils</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>com.github.yourusername</groupId> <artifactId>security-library</artifactId> <version>1.0.0</version> </dependency> </dependencies> </project>
Step 11: Advanced Configuration
Multi-Module Release Plugin:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>3.0.1</version>
<configuration>
<tagNameFormat>v@{project.version}</tagNameFormat>
<autoVersionSubmodules>true</autoVersionSubmodules>
<releaseProfiles>github</releaseProfiles>
<goals>deploy</goals>
</configuration>
</plugin>
</plugins>
</build>
Version Management:
<properties>
<revision>1.0.0</revision>
<sha1/>
<changelist/>
</properties>
<version>${revision}${sha1}${changelist}</version>
Step 12: Security Best Practices
GitHub Secrets Configuration:
GITHUB_TOKEN: Automatically provided by GitHub ActionsMAVEN_GPG_PRIVATE_KEY: For signing artifactsMAVEN_GPG_PASSPHRASE: GPG passphraseSONAR_TOKEN: For SonarCloud integration
.github/dependabot.yml:
version: 2 updates: - package-ecosystem: "maven" directory: "/" schedule: interval: "weekly" groups: dependencies: patterns: - "*"
Best Practices
1. Authentication & Security
- Use Fine-Grained Personal Access Tokens with minimal permissions
- Configure repository secrets for CI/CD
- Implement dependency scanning with Dependabot
- Use artifact signing for production releases
2. Versioning Strategy
<!-- Semantic Versioning --> <version>1.2.3</version> <!-- Release --> <version>1.2.3-SNAPSHOT</version> <!-- Snapshot --> <version>1.2.3-beta.1</version> <!-- Pre-release -->
3. Repository Management
- Use descriptive artifact names
- Include comprehensive documentation
- Provide source and javadoc artifacts
- Implement proper licensing
Common Issues & Solutions
1. Authentication Failures:
# Solution: Verify token permissions export GITHUB_TOKEN=ghp_... mvn deploy -s .m2/settings.xml
2. Repository Not Found:
<!-- Ensure repository URL matches your username/repo --> <url>https://maven.pkg.github.com/yourusername/your-repo</url>
3. Dependency Resolution:
<!-- Add repository to dependency resolution --> <repository> <id>github</id> <url>https://maven.pkg.github.com/owner/*</url> </repository>
Conclusion
GitHub Packages with Maven provides a powerful solution for Java package management that:
- Integrates seamlessly with your GitHub workflow
- Provides secure access control using GitHub permissions
- Enables efficient CI/CD with GitHub Actions
- Supports multi-module projects with complex dependencies
- Offers reliable artifact hosting with GitHub's infrastructure
By following this comprehensive guide, you can establish a robust package management system that scales with your Java projects while leveraging the full power of GitHub's ecosystem.