| 1 | --- |
| 2 | title: Maven |
| 3 | description: Deploy and depend on a workspace's Java and Kotlin libraries with Maven or Gradle, from a Maven repository of its own on g1t.sh, SNAPSHOTs included. |
| 4 | --- |
| 5 | |
| 6 | Every workspace has a Maven repository of its own, in the standard |
| 7 | layout that Maven, Gradle and every other JVM build tool read. `mvn |
| 8 | deploy` and Gradle's `publish` upload to it, and builds resolve |
| 9 | dependencies from it, private ones included. |
| 10 | |
| 11 | ```text |
| 12 | https://g1t.sh/-/maven/<workspace>/ |
| 13 | ``` |
| 14 | |
| 15 | Dependencies from Maven Central still come from Maven Central; only the |
| 16 | artifacts you deploy here come from g1t. |
| 17 | |
| 18 | ## Credentials |
| 19 | |
| 20 | Maven and Gradle send Basic credentials: any username, and an |
| 21 | [access token](https://g1t.sh/settings/tokens) as the password. A token |
| 22 | with full access works; one with scopes needs `packages:read` to resolve |
| 23 | private artifacts and `packages:write` to deploy. Public artifacts resolve |
| 24 | without one. |
| 25 | |
| 26 | For Maven, put the credentials in `~/.m2/settings.xml`, as a `<server>` |
| 27 | whose `id` is the one you give the repository in `pom.xml`. These pages use |
| 28 | the workspace's slug: |
| 29 | |
| 30 | ```xml |
| 31 | <settings> |
| 32 | <servers> |
| 33 | <server> |
| 34 | <id>acme</id> |
| 35 | <username>ada</username> |
| 36 | <password>${env.G1T_TOKEN}</password> |
| 37 | </server> |
| 38 | </servers> |
| 39 | </settings> |
| 40 | ``` |
| 41 | |
| 42 | Maven reads `${env.G1T_TOKEN}` from the environment, so the token stays |
| 43 | out of the file. |
| 44 | |
| 45 | For Gradle, keep them in `~/.gradle/gradle.properties`, named for the |
| 46 | repository (`acmeUsername` and `acmePassword` for a repository named |
| 47 | `acme`), or read them from the environment as the examples below do. |
| 48 | |
| 49 | ## Deploy with Maven |
| 50 | |
| 51 | Name the repository in `pom.xml`, for releases and SNAPSHOTs alike, and say |
| 52 | which repository the source is in: |
| 53 | |
| 54 | ```xml |
| 55 | <project> |
| 56 | <groupId>com.acme</groupId> |
| 57 | <artifactId>http-client</artifactId> |
| 58 | <version>0.3.1</version> |
| 59 | |
| 60 | <scm> |
| 61 | <url>https://g1t.sh/acme/http-client</url> |
| 62 | </scm> |
| 63 | |
| 64 | <distributionManagement> |
| 65 | <repository> |
| 66 | <id>acme</id> |
| 67 | <url>https://g1t.sh/-/maven/acme/</url> |
| 68 | </repository> |
| 69 | <snapshotRepository> |
| 70 | <id>acme</id> |
| 71 | <url>https://g1t.sh/-/maven/acme/</url> |
| 72 | </snapshotRepository> |
| 73 | </distributionManagement> |
| 74 | </project> |
| 75 | ``` |
| 76 | |
| 77 | ```sh |
| 78 | mvn deploy |
| 79 | ``` |
| 80 | |
| 81 | Maven uploads the artifact, its POM and their checksums, then the |
| 82 | artifact's `maven-metadata.xml`. The artifact is named by its coordinates, |
| 83 | `com.acme:http-client`, and its page on g1t.sh shows the description from |
| 84 | the POM of its highest release. |
| 85 | |
| 86 | ## Deploy with Gradle |
| 87 | |
| 88 | With the `maven-publish` plugin, in `build.gradle.kts`: |
| 89 | |
| 90 | ```kotlin |
| 91 | plugins { |
| 92 | `java-library` |
| 93 | `maven-publish` |
| 94 | } |
| 95 | |
| 96 | group = "com.acme" |
| 97 | version = "0.3.1" |
| 98 | |
| 99 | publishing { |
| 100 | publications { |
| 101 | create<MavenPublication>("library") { |
| 102 | from(components["java"]) |
| 103 | pom { |
| 104 | scm { url = "https://g1t.sh/acme/http-client" } |
| 105 | } |
| 106 | } |
| 107 | } |
| 108 | repositories { |
| 109 | maven { |
| 110 | name = "acme" |
| 111 | url = uri("https://g1t.sh/-/maven/acme/") |
| 112 | credentials(PasswordCredentials::class) |
| 113 | } |
| 114 | } |
| 115 | } |
| 116 | ``` |
| 117 | |
| 118 | ```sh |
| 119 | ./gradlew publish |
| 120 | ``` |
| 121 | |
| 122 | `credentials(PasswordCredentials::class)` reads `acmeUsername` and |
| 123 | `acmePassword` from `gradle.properties` or from the environment as |
| 124 | `ORG_GRADLE_PROJECT_acmeUsername` and `ORG_GRADLE_PROJECT_acmePassword`. |
| 125 | Gradle's Gradle Module Metadata (`.module`) is uploaded and served beside |
| 126 | the POM. |
| 127 | |
| 128 | ## Which repository an artifact belongs to |
| 129 | |
| 130 | The first file deployed makes the artifact. When a repository of the |
| 131 | workspace is named like its artifactId (`http-client`), it is linked to |
| 132 | that repository and has its visibility and roles: deploying needs Write |
| 133 | on it. Otherwise, the POM's `<scm><url>` (or its `<url>`) naming a g1t.sh |
| 134 | repository of the workspace links a new artifact to that repository, if you |
| 135 | may write to it. An artifact linked to neither is the workspace's, private, |
| 136 | and needs the workspace's Write base permission. See |
| 137 | [who can see and publish a package](/guides/packages/#who-can-see-and-publish-a-package). |
| 138 | |
| 139 | ## Depend on an artifact |
| 140 | |
| 141 | In `pom.xml`, name the repository, with the same `id` as the `<server>` |
| 142 | holding your credentials: |
| 143 | |
| 144 | ```xml |
| 145 | <repositories> |
| 146 | <repository> |
| 147 | <id>acme</id> |
| 148 | <url>https://g1t.sh/-/maven/acme/</url> |
| 149 | </repository> |
| 150 | </repositories> |
| 151 | |
| 152 | <dependencies> |
| 153 | <dependency> |
| 154 | <groupId>com.acme</groupId> |
| 155 | <artifactId>http-client</artifactId> |
| 156 | <version>0.3.1</version> |
| 157 | </dependency> |
| 158 | </dependencies> |
| 159 | ``` |
| 160 | |
| 161 | In Gradle: |
| 162 | |
| 163 | ```kotlin |
| 164 | repositories { |
| 165 | mavenCentral() |
| 166 | maven { |
| 167 | name = "acme" |
| 168 | url = uri("https://g1t.sh/-/maven/acme/") |
| 169 | credentials(PasswordCredentials::class) |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | dependencies { |
| 174 | implementation("com.acme:http-client:0.3.1") |
| 175 | } |
| 176 | ``` |
| 177 | |
| 178 | To fetch one artifact without a project: |
| 179 | |
| 180 | ```sh |
| 181 | mvn dependency:get -Dartifact=com.acme:http-client:0.3.1 \ |
| 182 | -DremoteRepositories=acme::default::https://g1t.sh/-/maven/acme/ |
| 183 | ``` |
| 184 | |
| 185 | ## SNAPSHOTs |
| 186 | |
| 187 | A version ending in `-SNAPSHOT` takes a new build each time it is |
| 188 | deployed. Maven and Gradle upload each build's files with a timestamp and |
| 189 | build number in their names (`http-client-0.4.0-20261006.120000-3.jar`), |
| 190 | and the version's `maven-metadata.xml` names the newest build of each file, |
| 191 | which is what a build depending on `0.4.0-SNAPSHOT` resolves. Earlier |
| 192 | builds stay, by their full names. |
| 193 | |
| 194 | ## Versions and files |
| 195 | |
| 196 | - **A release's files are written once.** Deploying a file of a released |
| 197 | version again with different content is refused with `409`; the same |
| 198 | content again is accepted, so a deploy that stopped part way can be run |
| 199 | again. Bump the version to change a release. |
| 200 | - **Checksums are worked out by g1t.** The `.md5`, `.sha1`, `.sha256` and |
| 201 | `.sha512` beside every file are answered from the file itself. Checksums |
| 202 | uploaded are checked against it, and a mismatch is refused with `400`. |
| 203 | - **`maven-metadata.xml` is made by g1t** from the versions there, so it |
| 204 | always lists every version, with the highest as `latest` and the highest |
| 205 | that is not a SNAPSHOT as `release`. The one a build uploads is accepted |
| 206 | and not kept. |
| 207 | - **The deploy's last step publishes it.** Maven and Gradle upload the |
| 208 | artifact's `maven-metadata.xml` after its files. Then each version (or |
| 209 | SNAPSHOT build) whose POM arrived in the deploy is published: it is an |
| 210 | audit entry and a `package.published` event, with every file in place. |
| 211 | A POM whose `groupId`, `artifactId` or `version` is not the one in its |
| 212 | path is refused with `400`. |
| 213 | |
| 214 | Delete a version, or the artifact, on its page on g1t.sh, with Admin on |
| 215 | the linked repository (an owner, for the workspace's own artifacts). |
| 216 | |
| 217 | ## Private and public artifacts |
| 218 | |
| 219 | An artifact linked to a repository has the repository's visibility; one of |
| 220 | the workspace's own is private until an owner makes it public on its page. |
| 221 | |
| 222 | | The workspace's artifacts | Without credentials | With credentials | |
| 223 | | --- | --- | --- | |
| 224 | | All public | Maven and Gradle download them. | The same; credentials are sent to deploy. | |
| 225 | | Some private | Every request without credentials is answered `401`, which makes Maven and Gradle send theirs. | Each artifact the credentials' owner may see. | |
| 226 | |
| 227 | A private artifact you cannot see looks exactly like one that does not |
| 228 | exist. |
| 229 | |
| 230 | ## In workflows |
| 231 | |
| 232 | A workflow's `G1T_TOKEN` is the workspace's own token for the run, and can |
| 233 | resolve and deploy the workspace's artifacts. With the `settings.xml` |
| 234 | above, give it to Maven as `G1T_TOKEN`: |
| 235 | |
| 236 | ```yaml |
| 237 | jobs: |
| 238 | deploy: |
| 239 | runs-on: ubuntu-latest |
| 240 | env: |
| 241 | G1T_TOKEN: ${{ secrets.G1T_TOKEN }} |
| 242 | steps: |
| 243 | - uses: actions/checkout@v4 |
| 244 | - run: | |
| 245 | mkdir -p ~/.m2 |
| 246 | printf '<settings><servers><server><id>acme</id><username>g1t</username><password>${env.G1T_TOKEN}</password></server></servers></settings>\n' > ~/.m2/settings.xml |
| 247 | - run: mvn --batch-mode deploy |
| 248 | ``` |
| 249 | |
| 250 | For Gradle, set `ORG_GRADLE_PROJECT_acmeUsername: g1t` and |
| 251 | `ORG_GRADLE_PROJECT_acmePassword: ${{ secrets.G1T_TOKEN }}` and run |
| 252 | `./gradlew publish`. |
| 253 | |
| 254 | ## Size |
| 255 | |
| 256 | Each file is one request and may be at most 100 MB. Without the |
| 257 | [g1t plan](/guides/usage-and-billing/#the-g1t-plan), a workspace's private |
| 258 | packages may hold 500 MB and its public ones 10 GB, as for |
| 259 | [container images](/guides/containers/#storage-and-pull-limits). A file is |
| 260 | stored once, by its content. |
| 261 | |
| 262 | ## Errors |
| 263 | |
| 264 | | Error | Means | |
| 265 | | --- | --- | |
| 266 | | `401` | No credentials, or a wrong or expired token. Check the `<server>` whose `id` matches the repository's, or Gradle's `acmeUsername` and `acmePassword`. | |
| 267 | | `403` | Signed in, but your role or your token's scopes do not allow it, or the workspace is out of free package storage. The response says which. | |
| 268 | | `404` | No such artifact, version or file, or a private one you cannot see. | |
| 269 | | `409` | That file of a release is already there with other content. Bump the version. | |
| 270 | | `400` | The upload was refused: a POM that does not match its path, or a checksum that does not match its file. The response says which. | |
| 271 | | `413` | The file is over 100 MB. | |