Build System

June 24, 2026 · View on GitHub

Monorepo Build Landscape

The monorepo contains multiple projects with different build tools:

DirectoryBuild ToolBuild Command (from repo root)
mockserver/Maven (./mvnw)cd mockserver && ./mvnw clean install
mockserver-ui/Vite + npmcd mockserver-ui && npm ci && npm run build
mockserver-node/Grunt + npmcd mockserver-node && npm ci && npx grunt
mockserver-client-node/npm + TypeScriptcd mockserver-client-node && npm ci && npm test
mockserver-client-python/pip + pytestcd mockserver-client-python && pip install -e '.[dev]' && pytest
mockserver-client-ruby/Bundler + RSpeccd mockserver-client-ruby && bundle install && bundle exec rspec
mockserver/mockserver-maven-plugin/Mavencd mockserver && ./mvnw clean install -DskipTests && ./mvnw -f mockserver-maven-plugin/pom.xml clean verify
mockserver-performance-test/k6 (JavaScript)cd mockserver-performance-test && for f in k6/*.js; do k6 inspect "$f"; done

Running the regression and growth scripts locally: see Performance regression pipeline — local runs below.

CI builds are orchestrated by .buildkite/scripts/generate-pipeline.sh which selects pipelines based on changed files. See CI/CD for details.

Local Development Behind a Corporate TLS-Inspection Proxy

TL;DR — if dependency downloads fail locally with TLS/certificate verify failed/unable to get local issuer certificate errors, your machine is behind a TLS-inspection proxy and the toolchains don't trust the corporate root CA. Point each toolchain at a combined CA bundle (system roots + corporate root). This is configured ONLY in your user/shell environment — never in repo files or pipeline scripts. CI agents have no proxy, so pipelines are unaffected and must stay that way.

Why this happens

A corporate TLS-inspection (MITM) proxy re-signs HTTPS traffic with a certificate chained to a corporate root CA. Package managers (npm, pip, cargo, composer, gem, go) and HTTP clients reject it because the corporate root is not in their default trust store. CI build agents run outside the proxy, so they never see this — which is exactly why the fix must live outside the repo.

One-time setup (new laptop / new engineer)

  1. Obtain the corporate root CA (PEM) from IT, e.g. tesco_root_ca.pem.
  2. Build a combined bundle (system roots + corporate root) once and keep it at a stable path, e.g. ~/.tesco-ca/ca-bundle-with-tesco.pem:
    mkdir -p ~/.tesco-ca
    # system roots from the OpenSSL/certifi default + the corporate root, concatenated:
    cat "$(python3 -m certifi 2>/dev/null || echo /etc/ssl/cert.pem)" ~/path/to/tesco_root_ca.pem \
      > ~/.tesco-ca/ca-bundle-with-tesco.pem
    
  3. Add the per-toolchain settings below to your shell profile (~/.zshrc) and user configs. Nothing here is committed.

Per-toolchain settings (user/shell scope only)

ToolchainWhere it runsSetting (local only)
Anything in Docker via .buildkite/scripts/run-in-docker.sh (PHP/Python/Ruby/Go/Rust/.NET/Node)containerexport LOCAL_DOCKER_CA_BUNDLE=~/.tesco-ca/ca-bundle-with-tesco.pem — the wrapper mounts it read-only and points every in-container tool at it (opt-in; unset in CI).
npm — direct and the mockserver/mockserver-netty Maven build-ui step (frontend-maven-plugin)host~/.npmrc with cafile=/Users/<you>/.tesco-ca/ca-bundle-with-tesco.pem (npm always reads user ~/.npmrc, so the Maven plugin's bundled node picks it up too).
Node (direct scripts)hostexport NODE_EXTRA_CA_CERTS=~/.tesco-ca/ca-bundle-with-tesco.pem
Python / pip / requestshostexport PIP_CERT=~/.tesco-ca/ca-bundle-with-tesco.pem REQUESTS_CA_BUNDLE=~/.tesco-ca/ca-bundle-with-tesco.pem SSL_CERT_FILE=~/.tesco-ca/ca-bundle-with-tesco.pem
Ruby / bundler / rubygemshostexport SSL_CERT_FILE=~/.tesco-ca/ca-bundle-with-tesco.pem BUNDLE_SSL_CA_CERT=~/.tesco-ca/ca-bundle-with-tesco.pem
Rust / cargohostexport CARGO_HTTP_CAINFO=~/.tesco-ca/ca-bundle-with-tesco.pem
Go (and git clone over HTTPS)hostexport SSL_CERT_FILE=~/.tesco-ca/ca-bundle-with-tesco.pem GIT_SSL_CAINFO=~/.tesco-ca/ca-bundle-with-tesco.pem
AWS CLI / SDK / Terraformhostexport AWS_CA_BUNDLE=~/.tesco-ca/ca-bundle-with-tesco.pem (see AGENTS.md → AWS Prerequisites)

The Java/Maven build itself usually works because the JDK trust store already chains Maven Central correctly; if a JVM HTTPS download fails (e.g. the frontend-maven-plugin downloading node from nodejs.org on a cold build), import the corporate root into a JDK trust store and pass -Djavax.net.ssl.trustStore=..., or pre-warm mockserver-netty/target/frontend once on a network without the proxy.

Non-negotiable: keep proxy config out of the repo and pipelines

  • Never add cafile/strict-ssl/proxy settings to a committed .npmrc, pom.xml, pip.conf, Cargo config, or any .buildkite/scripts/** step — that would break or skew CI, which has no proxy.
  • The only repo-side hook is the opt-in, env-gated LOCAL_DOCKER_CA_BUNDLE branch in run-in-docker.sh: it does nothing unless the variable is set, and CI never sets it.
  • All other settings live in your shell profile and user-level config files (~/.npmrc, ~/.zshrc).

Java Server Build (mockserver/)

Maven Configuration

MockServer uses Maven 3.9.16 via the Maven Wrapper (mvnw). The project targets Java 17 source/target compatibility — produced bytecode runs on Java 17+, and building from source requires JDK 17+.

mockserver/.mvn/maven.config sets -T 1C so the reactor builds with the parallel (one-thread-per-core) MultiThreadedBuilder by default, matching CI (./mvnw -T 1C clean install). This is a free speed-up on the multi-module compile/package phases and composes with the within-module parallel unit tests (see performance-tuning.md). Pass -T 1 on the command line to force a single-threaded build when debugging reactor ordering or interleaved log output.

Modules

The project comprises 25 Maven modules:

ModulePackagingPurpose
mockserver-bompom (flattened)Bill of Materials — import to pin every MockServer module and third-party transitive to one converged version
mockserver-testingjarShared test utilities
mockserver-client-javajarJava client API (MockServerClient)
mockserver-client-java-no-dependenciesjar (shaded)Client with all dependencies shaded
mockserver-corejarDomain model, matching, TLS, templates, codecs, event log, action handlers
mockserver-integration-testingjarIntegration test base classes
mockserver-integration-testing-no-dependenciesjar (shaded)Integration test base classes, shaded
mockserver-warwarServlet WAR deployment
mockserver-proxy-warwarProxy-only WAR deployment
mockserver-nettyjar (+fat, shaded)Netty server, CLI, dashboard, proxy relay
mockserver-netty-no-dependenciesjar (shaded)Netty server with all dependencies shaded
mockserver-junit-rulejarJUnit 4 @Rule integration
mockserver-junit-rule-no-dependenciesjar (shaded)JUnit 4 rule, shaded
mockserver-junit-jupiterjarJUnit 5 @ExtendWith integration
mockserver-junit-jupiter-no-dependenciesjar (shaded)JUnit 5 extension, shaded
mockserver-spring-test-listenerjarSpring Test integration
mockserver-spring-test-listener-no-dependenciesjar (shaded)Spring Test integration, shaded
mockserver-examples (at ../examples/java)jarUsage examples
mockserver-asyncjarAsyncAPI broker mocking
mockserver-testcontainersjarTestcontainers integration module
mockserver-state-infinispanjarInfinispan-backed clustered state backend
mockserver-blob-s3jarS3 blob storage backend
mockserver-blob-gcsjarGCS blob storage backend
mockserver-blob-azurejarAzure blob storage backend
mockserver-k8s-webhookjar (+fat)Kubernetes admission webhook for sidecar injection

Dependency management and the BOM

MockServer pins all of its third-party transitive versions in the parent POM's <dependencyManagement>, and the reactor's own Enforcer dependencyConvergence rule guards that everything resolves to a single version. That management is, by design, not inherited by downstream consumers — so a consumer running the same Enforcer rule would see MockServer's transitive versions diverge.

mockserver-bom closes that gap. It extends the parent POM (inheriting every third-party pin) and adds dependencyManagement entries for the published MockServer modules, then uses the flatten-maven-plugin (flattenMode=bom, dependencyManagement=expand) to publish a self-contained POM with all pins inlined and no parent reference. Consumers import it to get a converged tree:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.mock-server</groupId>
      <artifactId>mockserver-bom</artifactId>
      <version>${mockserver.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

Two related choices reduce convergence pressure even without the BOM: mockserver-client-java excludes the server-only engines from its mockserver-core dependency (a client never executes templates/scripts/WASM/gRPC), and mockserver-core prunes the stale velocity-engine-core 2.3 that velocity-tools-generic drags in alongside the 2.4.1 the build already uses. The parent's flatten-maven-plugin execution is declared <inherited>false</inherited>, so it applies only to the root POM; mockserver-bom carries its own copy.

Quick Reference

All Maven commands run from within the mockserver/ directory:

cd mockserver

# Full build (clean + compile + test + package)
./mvnw clean install

# Build without tests
./mvnw clean install -DskipTests

# Build a single module
./mvnw clean install -pl mockserver-core

# Run unit tests only
./mvnw test -pl mockserver-netty

# Run integration tests
./mvnw verify -pl mockserver-netty

Build Scripts

ScriptPurpose
scripts/buildkite_quick_build.shCI build — mvnw clean install with 8GB heap
scripts/buildkite_deploy_snapshot.shCI deploy — mvnw clean deploy to Sonatype snapshots
scripts/local_quick_build.shLocal build — Java 17, 3 threads, includes integration tests
scripts/local_online_build.shLocal build — Java 17, includes integration tests
scripts/local_buildkite_build.shRun Buildkite build locally inside Docker
scripts/local_build_module_by_module.shBuild each module sequentially
scripts/local_release.shMaven release (prepare + perform) to Sonatype staging
scripts/local_deploy_snapshot.shDeploy SNAPSHOT via Docker container
scripts/local_single_test.shRun a single integration test
scripts/local_single_module.shBuild a single module
scripts/stop_MockServer.shKill running MockServer processes
scripts/bash_functions.shShared shell functions library
scripts/download_maven_jars.shDownload Maven JARs from repositories
scripts/install_ca_certificate.shInstall CA certificates into trust stores
scripts/jekyll_server.shStart Jekyll development server
scripts/local_docker_launch.shLaunch interactive Docker Maven container
scripts/local_docker_push_tag.shPush Docker image with tag
scripts/local_generate_web_site.shGenerate Jekyll documentation website
scripts/local_javadoc_build_all_versions.shBuild Javadoc for all versions
scripts/local_list_versions.shList project versions
scripts/log_event_size_test_*.shLog event size test variants (4 scripts)

Maven Profiles

graph LR
    subgraph "Profiles"
        KM["kill_mockserver_instances
Auto-activates on Unix"]
        DJ["disable-java8-doclint
Auto-activates on JDK 8+"]
        RL["release
Manual activation"]
    end

    KM -->|clean phase| STOP["scripts/stop_MockServer.sh"]
    DJ -->|sets| DOCLINT["-Xdoclint:none"]
    RL -->|adds| SRC[maven-source-plugin]
    RL -->|adds| JD[maven-javadoc-plugin]
    RL -->|adds| GPG[maven-gpg-plugin]
    RL -->|adds| REL[maven-release-plugin]
ProfileActivationPurpose
kill_mockserver_instancesAuto on Unix (/usr/bin/env exists)Kills existing MockServer processes during clean phase
disable-java8-doclintAuto on JDK 8+Disables strict Javadoc linting
releaseManual (-P release)Adds source JARs, Javadoc JARs, GPG signing, Maven release plugin

Build Plugins

PluginVersionPhasePurpose
maven-compiler-plugin3.15.0compileJava 17 compilation with -Xlint:all
git-commit-id-maven-plugin9.0.1initializeResolves the abbreviated git commit hash into ${git.commit.id.abbrev} for the version class (mockserver-core only); degrades to an empty hash when no git metadata is present
templating-maven-plugin3.1.0generate-sourcesGenerates version class from templates (version, group/artifact id, git hash)
maven-jar-plugin3.5.0packageJAR packaging with MANIFEST.MF metadata
maven-clean-plugin3.5.0cleanRemoves .log, keystore, and temp files
maven-surefire-plugin3.5.6testUnit tests (*Test.java, excludes *IntegrationTest.java)
maven-failsafe-plugin3.5.6integration-testIntegration tests (*IntegrationTest.java)
maven-checkstyle-plugin3.6.0validateCode style enforcement via checkstyle.xml
maven-enforcer-plugin3.6.3validateDependency convergence checks
exec-maven-plugin3.6.3cleanRuns stop_MockServer.sh (Unix profile)

Test Configuration

  • Unit tests: *Test.java — run during test phase via Surefire
  • Integration tests: *IntegrationTest.java — run during integration-test/verify phases via Failsafe
  • Parallel unit tests (mockserver-core): Surefire runs in two phases — a parallel phase (parallel=classes, threadCount=4) for the bulk of the suite, and a sequential-tests execution (parallel=none) for the classes that mutate JVM-global state (ConfigurationProperties system properties, the static Prometheus Metrics registry, the controllable clock, or globally-fixed time). ParallelStaticStateGuardTest fails the build if the parallel-excluded and sequential-included class lists drift apart. See performance-tuning.md for the rationale.
  • Log level: mockserver.logLevel=ERROR during tests
  • Locale: Forced to en-GB (-Duser.language=en -Duser.country=GB)
  • Test listener: org.mockserver.test.PrintOutCurrentTestRunListener for progress output

Packaging Outputs

The mockserver-netty module produces multiple artifacts:

graph TD
    MN[mockserver-netty]
    MN --> JAR[Standard JAR]
    MN --> FAT["jar-with-dependencies
Fat JAR with all deps"]
    MN --> SHADED["Shaded JAR
Relocated packages"]
    MN --> BREW["brew-tar
Homebrew tarball"]
    MN --> DEB["Debian Package
.deb with init.d"]
ArtifactClassifierDescription
Standard JAR(none)Module classes only
Fat JARjar-with-dependenciesAll dependencies bundled (used by Docker)
Shaded JARshadedDependencies relocated to avoid conflicts
Homebrew tarballbrew-tarTarball for Homebrew formula
Debian package(none).deb with SysV init.d and Upstart configs

Distribution

Artifacts are published to:

  • Central Portal (snapshots): https://central.sonatype.com/repository/maven-snapshots/
  • Maven Central (releases): via Central Portal at https://central.sonatype.com/repository/maven-releases/

GPG signing is required for releases (configured in the release profile).

Performance regression pipeline — local runs

mockserver-performance-test/k6/regression.js and growth.js can be run locally against the compose stack in mockserver-performance-test/stack/. The forward behaviour requires a dedicated upstream MockServer because it proxies to a separate instance (not itself). Use K6_FORWARD_SELF=true to skip the upstream requirement on a single-container local smoke run.

# Start the compose stack (includes mockserver + mockserver-upstream)
cd mockserver-performance-test/stack
docker compose up -d

# Run regression over HTTP (default)
k6 run \
  -e BASE_URL=http://localhost:1080 \
  -e K6_RESULT_PATH=/tmp/perf-result.json \
  mockserver-performance-test/k6/regression.js

# Run regression over HTTPS + HTTP/2
k6 run \
  -e BASE_URL=https://localhost:1080 \
  -e PROTO=https_h2 \
  -e K6_RESULT_PATH=/tmp/perf-result-h2.json \
  mockserver-performance-test/k6/regression.js

# Run growth (fills maxLogEntries, measures latency slope)
k6 run \
  -e BASE_URL=http://localhost:1080 \
  mockserver-performance-test/k6/growth.js

# Single-container smoke (no upstream needed)
k6 run \
  -e K6_FORWARD_SELF=true \
  -e BASE_URL=http://localhost:1080 \
  mockserver-performance-test/k6/regression.js

The upstream container is named mockserver-upstream and listens on port 1080 inside the stack network (FORWARD_UPSTREAM_HOST defaults to mockserver-upstream:1080). For local runs where k6 is on the host, set FORWARD_UPSTREAM_HOST=localhost:<exposed-port> or use K6_FORWARD_SELF=true.

Result JSON schema

The pipeline compare step (perf-test-compare.sh) merges two artifacts and persists the result to S3. The schemas are:

perf-result.json

{
  "metadata": { "sha": "...", "branch": "...", "timestamp": "..." },
  "behaviours": {
    "<op>_<proto>": { "p50_ms": 0, "p95_ms": 0, "p99_ms": 0, "throughput_rps": 0, "error_rate": 0 }
  },
  "growth": {
    "cpu_peak": 0, "heap_start": 0, "heap_end": 0, "heap_peak": 0, "heap_ratio": 0,
    "gc_seconds_delta": 0, "threads_peak": 0, "p95_start": 0, "p95_end": 0, "p95_ratio": 0
  }
}

perf-microbench.json

{
  "microbench": {
    "<matcherType>_<count>": { "time_per_op": 0, "time_unit": "ns/op", "alloc_bytes_per_op": 0 }
  }
}

<op>_<proto> keys are match_http, forward_http, template_http, large_http, and their _https_h2 counterparts. The merged run is stored at s3://mockserver-ci-perf-results/runs/<branch>/<iso>__<sha>.json.