Synchronous HTTP API for the MobilityData Canonical GTFS Schedule Validator.
Submit a GTFS feed (by URL or upload) and receive the full validation report in a single
response, as JSON or as the rendered HTML document. The Spring Boot server code is
generated from the OpenAPI schema (docs/GTFSValidatorAPI.yaml) using the
openapi-generator-maven-plugin (spring generator, delegate pattern), and the endpoints are
implemented on top of the published validator core Maven artifacts
(org.mobilitydata.gtfs-validator).
This module replaces gtfs-validator/web/service.
The API project version is independent of the validator core version:
| Version | Where | Current |
|---|---|---|
| API project version | git tag vX.Y.Z (derived at build time) |
see releases |
| Validator core (dependency) | gtfs-validator.version property |
8.0.1 |
| OpenAPI spec version | docs/GTFSValidatorAPI.yaml info.version |
2.0.0 |
The API project version is derived from git by the
maven-git-versioning-extension
(configured in .mvn/): a release tag vX.Y.Z yields version X.Y.Z, while any
other build (commits on main, PRs, local checkouts) yields a patch-bumped
SNAPSHOT of the latest tag (e.g. 1.0.1-SNAPSHOT after v1.0.0). The
<version> in pom.xml is only a placeholder and is never edited for a release —
see Releasing.
The validator core version is reported at runtime by GET /v2/metadata.
Base path: /v2.
| Method | Path | Description |
|---|---|---|
GET |
/metadata |
Service metadata (validator version, limits). |
POST |
/validate |
Validate a feed by URL (application/json body {url, countryCode?}). |
POST |
/validate-upload |
Validate an uploaded GTFS ZIP (multipart/form-data). |
Both validation endpoints negotiate the response format via the Accept header:
application/json(default) — the structuredValidationReport(mirrorsreport.json).text/html— the rendered HTML report.- any other value —
406 Not Acceptable.
Interactive API docs (Swagger UI) are served at /swagger-ui.html; the raw spec at /GTFSValidatorAPI.yaml.
Requires JDK 17.
mvn clean package # generate, compile, test, and build the executable jar
mvn test # run the integration tests only
mvn verify # also runs the spotless code-style checkThe default build uses the stable validator core release (gtfs-validator.version,
currently 8.0.1). To build against a pre-release SNAPSHOT of the validator
core instead, activate the opt-in snapshot profile:
mvn -Psnapshot clean package # uses the pinned snapshot (8.0.2-SNAPSHOT)
# override the snapshot version explicitly:
mvn -Psnapshot -Dgtfs-validator.version=8.0.2-SNAPSHOT clean packageThe profile enables the public Maven Central snapshot repository
(no credentials needed) — required because Maven only resolves -SNAPSHOT
artifacts from repositories that explicitly enable snapshots. The API's own
version is unaffected; only the validator core dependency changes.
Matches the gtfs-validator repo:
google-java-format (version 1.25.2)
enforced via the Spotless Maven plugin. The check runs during mvn verify.
mvn spotless:apply # auto-format sources
mvn spotless:check # verify formatting (also part of `mvn verify`)mvn spring-boot:run
# or
java -jar target/gtfs-validator-api-1.0.0.jarThe service listens on port 8080.
# Metadata
curl http://localhost:8080/v2/metadata
# Validate by upload (JSON report)
curl -X POST http://localhost:8080/v2/validate-upload \
-F "file=@feed.zip" -F "countryCode=CA" \
-H "Accept: application/json"
# Validate by upload (HTML report)
curl -X POST http://localhost:8080/v2/validate-upload \
-F "file=@feed.zip" -H "Accept: text/html" -o report.html
# Validate by URL
curl -X POST http://localhost:8080/v2/validate \
-H "Content-Type: application/json" \
-d '{"url":"https://example.org/gtfs.zip","countryCode":"CA"}'docker build -t gtfs-validator-api:1.0.0 .
docker run --rm -p 8080:8080 gtfs-validator-api:1.0.0Pass JVM options via JAVA_OPTS, e.g. -e JAVA_OPTS="-Xmx4g".
To build an image against a validator-core SNAPSHOT, pass the MAVEN_PROFILES
build arg:
docker build --build-arg MAVEN_PROFILES=snapshot -t gtfs-validator-api:snapshot .CI publishes multi-arch images (linux/amd64, linux/arm64) to the GitHub
Container Registry. The image tag encodes both the API version (derived from the
git tag) and the validator core version. Two variants are published per build:
| Variant | Example release tag | Example main-merge tag | Validator core |
|---|---|---|---|
Stable (stable-core) |
1.0.0-validator8.0.1 (+ latest) |
1.0.1-SNAPSHOT-validator8.0.1 |
stable release |
Snapshot (snapshot-core) |
1.0.0-validator8.0.2-SNAPSHOT |
1.0.1-SNAPSHOT-validator8.0.2-SNAPSHOT |
pre-release SNAPSHOT |
The tag format is <apiVersion>-validator<validatorCoreVersion>: the validator
infix scopes the trailing version to the validator core, not the API (the two
versions evolve independently). Both variants are published on every merge to
main (as API snapshots, version a patch-bumped SNAPSHOT of the latest tag, e.g.
1.0.1-SNAPSHOT) and on every release (versioned
X.Y.Z); the stable variant additionally gets latest on releases only. The
snapshot variant is never tagged latest. See Releasing for how
versions are produced.
The images live at ghcr.io/mobilitydata/gtfs-validator-api. They are public, so
no login is required to pull. Browse all available tags on the
package page.
Stable — recommended for normal use; built against a released validator core:
# `latest` always points at the most recent release
docker pull ghcr.io/mobilitydata/gtfs-validator-api:latest
docker run --rm -p 8080:8080 ghcr.io/mobilitydata/gtfs-validator-api:latest
# …or pin an exact, immutable version
docker run --rm -p 8080:8080 \
ghcr.io/mobilitydata/gtfs-validator-api:1.0.0-validator8.0.1Snapshot — for trying the latest validator core before it is released; built
against a -SNAPSHOT of the validator. The tag is re-published as the upstream
snapshot moves, so re-pull to get the newest build:
docker pull ghcr.io/mobilitydata/gtfs-validator-api:1.0.0-validator8.0.2-SNAPSHOT
docker run --rm -p 8080:8080 \
ghcr.io/mobilitydata/gtfs-validator-api:1.0.0-validator8.0.2-SNAPSHOTOnce a container is running, the API is available on port 8080 regardless of
which image you chose:
# Confirm which validator core the running image uses
curl http://localhost:8080/v2/metadata
# Validate a feed
curl -X POST http://localhost:8080/v2/validate-upload \
-F "file=@feed.zip" -F "countryCode=CA" -H "Accept: application/json"Pass JVM options via JAVA_OPTS, e.g. -e JAVA_OPTS="-Xmx4g", and activate the
structured-logging profile with -e SPRING_PROFILES_ACTIVE=json. If you have
pinned the package to private visibility, authenticate first:
echo "$GITHUB_TOKEN" | docker login ghcr.io -u <username> --password-stdinThe API version is derived from the git tag at build time by the
maven-git-versioning-extension
(see .mvn/); pom.xml keeps the placeholder 0.0.0-SNAPSHOT and is never
edited by hand for a release. To cut a release and publish images:
-
Make sure
mainis green and the desired changes are merged. -
Create and push a semver tag prefixed with
v:git tag v1.0.0 git push origin v1.0.0
(Or publish a GitHub Release with that tag — either triggers the same flow.)
-
The
docker.ymlworkflow then builds both variants and publishes:- stable:
ghcr.io/<owner>/gtfs-validator-api:1.0.0-validator<core>and:latest - snapshot:
ghcr.io/<owner>/gtfs-validator-api:1.0.0-validator<coreSnapshot>
- stable:
Notes:
- Every merge to
mainpublishes both variants as API snapshots (a patch-bumped SNAPSHOT of the latest tag, e.g.1.0.1-SNAPSHOT-validator8.0.1afterv1.0.0), without movinglatest. - A release (
v*tag / GitHub Release) publishes both variants versioned (X.Y.Z-…); the stable variant also updateslatest. - Pull requests build both variants to validate the Dockerfile but publish nothing.
- The tag must match
v+ semver (e.g.v1.2.3); other tags don't set the version.
Image tags follow <apiVersion>-validator<validatorCoreVersion>. The API version
comes from git (a release tag, or a patch-bumped SNAPSHOT of the latest tag
otherwise) and the validator core version comes from the build variant. These
are independent: a main build is an API snapshot, which is not the same thing
as the validator-core snapshot.
On merge to main the API version resolves to a patch-bumped SNAPSHOT of the
latest tag (e.g. 1.0.1-SNAPSHOT after v1.0.0), so both variants publish as API
snapshots (and latest is not moved):
| Variant | Tag |
|---|---|
| stable-core | 1.0.1-SNAPSHOT-validator8.0.1 |
| snapshot-core | 1.0.1-SNAPSHOT-validator8.0.2-SNAPSHOT |
On release (vX.Y.Z tag / GitHub Release) the API version resolves to X.Y.Z:
| Variant | Tag |
|---|---|
| stable-core | X.Y.Z-validator8.0.1 + latest |
| snapshot-core | X.Y.Z-validator8.0.2-SNAPSHOT |
On pull requests both variants build (to validate the Dockerfile) but nothing is published.
GitHub Actions workflows live in .github/workflows/:
| Workflow | Trigger | Purpose |
|---|---|---|
build.yml |
push / PR to main/master, manual |
mvn clean verify — OpenAPI generation, compile, integration tests and the Spotless code-style check; uploads the built jar. |
docker.yml |
push / PR / tag v* / release, manual |
Builds two image variants (stable and validator-SNAPSHOT) via a matrix, multi-arch. On non-PR events it pushes to ghcr.io/<owner>/gtfs-validator-api tagged <api>-validator<core>: main merges publish API snapshots (a patch-bumped SNAPSHOT of the latest tag, e.g. 1.0.1-SNAPSHOT-…), releases publish versioned images with latest on the stable variant. PRs build both but push neither. See Releasing. |
Key properties (see src/main/resources/application.properties):
| Property | Default | Description |
|---|---|---|
openapi.gTFSValidator.base-path |
/v2 |
API base path. |
spring.servlet.multipart.max-file-size |
-1 (unlimited) |
Max upload size; set a concrete value to cap it. |
gtfs.validator.limits.max-upload-bytes |
(unset) | maxUploadBytes advertised in /metadata. |
gtfs.validator.limits.max-requests-per-minute |
(unset) | maxRequestsPerMinute advertised in /metadata. |
By default the service logs human-readable plain text to the console — convenient for
local development. Activate the json profile to switch the console to structured
JSON (one object per line, with severity, message, ISO-8601 timestamp, logger,
thread, MDC values and structured key/value pairs; exception stack traces are folded
into message). This format is understood by cloud log aggregators that parse stdout.
# Local: plain text (default, no profile)
mvn spring-boot:run
# Structured JSON (e.g. in a container / cloud)
SPRING_PROFILES_ACTIVE=json java -jar target/gtfs-validator-api-*.jar
# or in Docker:
docker run -e SPRING_PROFILES_ACTIVE=json -p 8080:8080 gtfs-validator-api:1.0.0Implemented with Spring Boot's built-in structured logging
(no extra dependencies); see JsonLogFormatter and application-json.properties.
Logs from the validator core and other libraries are captured in the same format:
the core logs via Flogger/java.util.logging, which Spring Boot bridges to SLF4J →
Logback → this formatter. (The pom excludes the stray commons-logging jar pulled in
by commons-validator so JCL is handled by spring-jcl and not emitted as raw,
non-JSON lines.)
docs/GTFSValidatorAPI.yaml # OpenAPI single source of truth (generator input + served spec)
src/main/java/.../api/Application.java # Spring Boot entry point
src/main/java/.../api/handler/ # delegate handlers + validator integration
src/main/java/.../api/logging/ # structured JSON log formatter (json profile)
target/generated-sources/openapi/ # generated API interfaces + models
target/classes/static/GTFSValidatorAPI.yaml # spec copied here at build time, served at /GTFSValidatorAPI.yaml