Open-Source Wikis

/

Elasticsearch

/

How to contribute

/

Development workflow

elastic/elasticsearch

Development workflow

The inner loop most contributors use day to day.

Branch and rebase

git checkout main
git pull --rebase upstream main
git checkout -b my-feature-branch

Always rebase on top of main rather than merging it into your branch. The maintainers will squash on merge.

Build incrementally

Most edits do not require a full build. Use targeted Gradle tasks:

# Compile a single subproject
./gradlew :server:compileJava

# Run only the tests for that subproject
./gradlew :server:test

# Run the precommit gates for one subproject
./gradlew :server:precommit

The precommit task is the meta-task wired in build-tools-internal/src/main/groovy/elasticsearch.precommit.gradle. It runs Spotless, forbidden-apis, third-party audit, license-header check, JarHell, and a handful of project-specific checks.

Reproduce a failing test from CI

Buildkite's failure logs include a REPRODUCE WITH: line. Copy and run it verbatim — it captures the full Gradle command, including -Dtests.seed, JVM args, and the --tests selector. Example:

./gradlew :server:test \
  --tests "org.elasticsearch.cluster.coordination.CoordinatorTests.testElectionSchedulerStaysCalmIfDoesNotKnowAboutMasterCandidates" \
  -Dtests.seed=DEADBEEF -Dtests.locale=en-US -Dtests.timezone=UTC \
  -Dtests.iters=20

Run a multi-node cluster from source

./gradlew :run -PnumNodes=3

Or, for a more realistic setup with x-pack security enabled and pre-configured users:

./gradlew :x-pack:plugin:run

Each node logs to build/run/<node-name>/logs/. Stop with Ctrl+C.

Debugging

Attach a debugger by adding --debug-jvm to any test or run task. The JVM listens on port 5005:

./gradlew :server:test --tests <ClassName> --debug-jvm

For a running cluster:

./gradlew :run --debug-jvm

Format and lint before pushing

./gradlew spotlessApply
./gradlew :server:precommit

spotlessApply reorders imports and prunes unused imports; running it once before every push prevents review churn.

Adding a new transport action

The mechanical recipe (see Action layer for the longer version):

  1. Define a new ActionType<Response> in a *Action constants class.
  2. Implement Transport*Action extends HandledTransportAction<Request, Response> (or one of the master / shard / nodes-fanout variants).
  3. Implement a Rest*Action extends BaseRestHandler and register HTTP routes.
  4. Register the action in ActionModule (or in a plugin's getActions() for plugin-contributed actions).
  5. Add a YAML REST test under the relevant rest-api-spec/test/ or per-module src/yamlRestTest/.
  6. Add unit tests for the request/response serialization (Writeable round-trips) and the handler logic.

Adding a new setting

Setting<T> instances live next to the service that uses them. Register cluster/index settings via the plugin's getSettings() and IndexScopedSettings machinery.

public static final Setting<TimeValue> MY_TIMEOUT_SETTING = Setting.timeSetting(
    "indices.my_feature.timeout", TimeValue.timeValueSeconds(30), Property.NodeScope, Property.Dynamic);

If the setting is dynamic, register a listener with ClusterSettings or IndexSettings so changes are picked up without a restart.

Common dev pitfalls

  • Wildcard imports: never. Spotless will fix them, but avoid them in the diff.
  • Editing generated files: ANTLR grammars, transport version CSVs, and a few REST spec files are regenerated. Edit the source instead.
  • File vs Path: prefer NIO Path. forbidden-apis blocks most uses of java.io.File outside of legacy code paths.
  • Direct Thread creation: use EsExecutors and the named thread pools.
  • Global state: the codebase avoids singletons; always wire dependencies through the constructor.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Development workflow – Elasticsearch wiki | Factory