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.