# Maven Central Scraper: Java Artifacts & Coordinates (`arman-bd/maven-central-scraper`) Actor

Search Maven Central for Java artifacts: groupId, artifactId, version history, packaging, classifiers and publish timestamps. No login, no proxy, no browser.

- **URL**: https://apify.com/arman-bd/maven-central-scraper.md
- **Developed by:** [Arman Hossain](https://apify.com/arman-bd) (community)
- **Categories:** Developer tools, Automation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.37 / 1,000 artifact scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Maven Central Scraper: Java Artifacts & Coordinates

![Maven Central Scraper: Java artifacts by coordinate or group, group ID, artifact ID, version, packaging and publish date](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/maven-central-scraper.jpg)

Searches the Maven Central index for JVM artifacts and returns structured rows. You get `groupId`, `artifactId`, version, packaging, the classifier list, version count, publish timestamp and ready-made links to the artifact page and the raw repository directory.

Maven Central publishes a searchable index over the repository, and this Actor reads it directly. There's no browser, no proxy, no login and no credentials to manage. One query is one request, 200 rows come back per request, and paging is automatic.

**Agent skill: [SKILL.md](https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/maven-central-scraper.md)**

```
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/maven-central-scraper.md
```

### What you get

| Field | What it holds |
|---|---|
| `query` | The normalised Solr query the row came from |
| `coordinate` | Full Maven coordinate: `group:artifact` in latest mode, `group:artifact:version` in per-version mode |
| `groupId`, `artifactId`, `version` | The coordinate split into parts |
| `isLatestVersion` | `true` when the row is the artifact's newest version, `false` for historical version rows |
| `versionCount` | Total published versions, in latest mode only, `null` otherwise |
| `packaging` | `jar`, `pom`, `bundle`, `aar`, `war` and friends |
| `classifiers` | Every file extension and classifier published for the version, such as `["-sources.jar", "-javadoc.jar", ".jar", ".pom"]` |
| `keywords` | Description keywords from the index, per-version mode only |
| `repositoryId` | Source repository, in practice always `central`, latest mode only |
| `publishedAt`, `publishedAtMs` | Publish time as ISO 8601 and as the raw epoch milliseconds Maven returns |
| `artifactUrl`, `versionUrl` | Sonatype Central pages for the artifact and for that exact version |
| `repositoryUrl` | The `repo1.maven.org` directory holding the actual files |
| `scrapedAt` | Run timestamp |

`RUN_SUMMARY` in the key-value store holds per-run counts, the filters you used, and any query that failed.

### Use cases

- **SBOM and dependency audits.** Resolve every coordinate in a lockfile to its true latest version and publish date.
- **Outdated-dependency reports.** Compare what your builds pin against the latest version across whole groups.
- **Release-history reconstruction.** Turn `latestOnly` off and get every version a library ever shipped, with dates.
- **Supply-chain checks.** Spot artifacts with no `-sources.jar`, unexpected packaging, or a suspiciously recent first publish.
- **OSS traction tracking.** Release cadence per quarter is a decent proxy for whether a project is alive.

### Quick start

Two artifacts, newest version of each:

```json
{
 "queries": ["com.google.guava:guava", "org.springframework:spring-core"]
}
```

Everything a group publishes, jars only:

```json
{
 "queries": ["g:com.fasterxml.jackson.core", "g:org.apache.commons"],
 "latestOnly": true,
 "maxResultsPerQuery": 200,
 "packagingFilter": ["jar", "bundle"]
}
```

Full release history for one library, one row per version:

```json
{
 "queries": ["org.springframework:spring-core"],
 "latestOnly": false,
 "maxResultsPerQuery": 500
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `queries` | array | required | Solr syntax like `g:com.google.guava`, a coordinate like `com.google.guava:guava` which is expanded to `g:. AND a:.`, or free-text keywords. |
| `latestOnly` | boolean | `true` | On gives one row per artifact, newest version. Off gives one row per published version. |
| `maxResultsPerQuery` | integer | `100` | Rows saved per query. 0 means everything the query matches. Paged 200 at a time. |
| `packagingFilter` | array | `[]` | Keep only these packaging types. Applied after fetching, so it does not reduce request count. Empty means all. |

Free text only works in latest mode. The per-version index has no free-text field and answers HTTP 400 to keyword searches. The Actor checks for this before sending anything and records a query-specific message in `RUN_SUMMARY.failures` telling you to switch `latestOnly` on or name a group.

### Output example

A real record from `com.google.guava:guava`:

```json
{
 "query": "g:com.google.guava AND a:guava",
 "coordinate": "com.google.guava:guava",
 "groupId": "com.google.guava",
 "artifactId": "guava",
 "version": "33.4.8-jre",
 "isLatestVersion": true,
 "versionCount": 150,
 "packaging": "bundle",
 "classifiers": [".module", "-sources.jar", ".pom", "-javadoc.jar", ".jar"],
 "keywords": [],
 "repositoryId": "central",
 "publishedAt": "2025-04-14T17:25:22.422Z",
 "publishedAtMs": 1744651522422,
 "artifactUrl": "https://central.sonatype.com/artifact/com.google.guava/guava",
 "versionUrl": "https://central.sonatype.com/artifact/com.google.guava/guava/33.4.8-jre",
 "repositoryUrl": "https://repo1.maven.org/maven2/com/google/guava/guava/33.4.8-jre/",
 "scrapedAt": "2026-08-06T11:45:18.892Z"
}
```

The same query with `latestOnly: false` returns 150 rows instead of one. Same coordinate, one row per version, each with its own `publishedAt`, `packaging` and `classifiers`, plus `keywords` filled in and `versionCount` and `repositoryId` set to `null`.

### Writing a query

| You want | Write |
|---|---|
| One artifact | `com.google.guava:guava` or `g:com.google.guava AND a:guava` |
| One exact version | `com.google.guava:guava:33.4.8-jre` |
| Everything in a group | `g:org.apache.commons` |
| Every group publishing a given artifact name | `a:spring-core` |
| Keyword search, latest mode only | `kafka client` |

Coordinates are the shortcut: `group:artifact` and `group:artifact:version` are rewritten into proper Solr clauses before the request. Anything already containing `g:`, `a:`, `v:`, `AND` or `OR` is passed through untouched, so full Solr expressions work as they are.

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~maven-central-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "queries": ["com.google.guava:guava", "g:org.apache.commons"],
 "latestOnly": true,
 "maxResultsPerQuery": 50
 }'
```

### JavaScript example

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/maven-central-scraper').call({
 queries: ['org.springframework:spring-core'],
 latestOnly: false,
 maxResultsPerQuery: 300,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const a of items) console.log(`${a.coordinate} (${a.packaging}) published ${a.publishedAt}`);
```

### Limits and behaviour

- The index caps out at 200 rows per request. Solr accepts larger `rows` values but silently falls back to 20 above 200, so the Actor pages at exactly 200 and advances by the number of documents actually returned.
- The index is intermittently slow. Requests that normally take 400 ms occasionally stall for a minute or more. Each request gets 30 seconds and up to three attempts with linear backoff, and a stalled request usually succeeds on the retry. Give broad runs a generous Actor timeout.
- There are two indexes with two shapes. Latest mode returns `versionCount` and `repositoryId` but no keywords, and per-version mode returns `keywords` but neither of the other two. Both shapes are emitted with the same key set and `null` where the index has nothing, so records never gain or lose fields.
- Timestamps are publish times, not release announcements. `publishedAt` is when the file landed in Central, which for a staged release is usually within minutes of the announcement rather than identical to it.
- Filters are applied after fetching. `packagingFilter` reduces the dataset, not the number of requests, and `maxResultsPerQuery` counts rows saved, so a strict filter pages further to fill the quota.
- Public data only. No authentication, no personal data, nothing that gets around access controls.

### FAQ

**Do I need a Maven Central account?** No. You supply no credentials.

**Why did `latestOnly: false` reject my keyword search?** The per-version index only supports field queries. Name a group or artifact with `g:`, `a:`, or a coordinate, or leave `latestOnly` on.

**How many versions can I get for one artifact?** All of them. Spring Core returns 311 rows, and `maxResultsPerQuery: 0` removes the cap.

**Does it download the JARs?** No. It returns `repositoryUrl`, the directory on `repo1.maven.org` holding the `.jar`, `.pom` and signatures, so you can fetch exactly what you need.

**Can I tell whether a version is a pre-release?** Not from a dedicated field, because Maven has none. The version string carries it: `-M6`, `-RC1`, `-SNAPSHOT`, `-alpha`. Filter on `version` yourself.

**Can I plug it into something else?** Yes. Apify API, the client libraries, webhooks, scheduled runs, dataset exports to JSON, CSV or Excel, or MCP. The output is structured JSON.

# Actor input Schema

## `queries` (type: `array`):

One search per entry. Accepts Solr syntax ('g:com.google.guava', 'a:jackson-databind'), a coordinate like 'com.google.guava:guava' which is expanded for you, or free text matched against name, description and tags. Free text works only with 'Latest version only' switched on.

## `latestOnly` (type: `boolean`):

On (default): one row per artifact showing its newest version, total version count and repository. Off: one row per published version, which is how you get a release history, so expect hundreds of rows for a mature library. Switching this off also requires every query to name a group or artifact, because free-text keywords are rejected by the per-version index.

## `maxResultsPerQuery` (type: `integer`):

Cap rows saved per query. The API returns at most 200 rows per request, so higher values page automatically. Set 0 for everything the query matches.

## `packagingFilter` (type: `array`):

Keep only artifacts with one of these packaging types, e.g. 'jar', 'pom', 'bundle', 'aar'. Leave empty for all.

## Actor input object example

```json
{
  "queries": [
    "g:com.fasterxml.jackson.core",
    "a:spring-core",
    "kafka client"
  ],
  "latestOnly": true,
  "maxResultsPerQuery": 100,
  "packagingFilter": [
    "jar",
    "bundle"
  ]
}
```

# Actor output Schema

## `items` (type: `string`):

Every record the run produced.

## `runsummary` (type: `string`):

The RUN_SUMMARY record from the run's key-value store.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "queries": [
        "com.google.guava:guava",
        "g:org.apache.commons"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/maven-central-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "queries": [
        "com.google.guava:guava",
        "g:org.apache.commons",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/maven-central-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "queries": [
    "com.google.guava:guava",
    "g:org.apache.commons"
  ]
}' |
apify call arman-bd/maven-central-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arman-bd/maven-central-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/75UyGDyVLJifmDPLR/builds/tF6FVwculb3O4eXMT/openapi.json
