Skip to content

Developing Plugins#

Getting Started#

Concourse plugins must be implemented as Java classes that extend com.cinchapi.concourse.server.plugin.Plugin. To get started, create a Java project that depends on the concourse-plugin-core framework.

Create a build.gradle File#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
plugins {
    id "com.cinchapi.concourse-plugin" version "1.0.14"
    id 'java'
    id 'eclipse'
    id 'maven'
}

group = 'com.cinchapi'
version  = getVersion()

// Set the version for all Concourse dependencies
ext.concourseVersion = '1.0.0'

// Configure Concourse plugin
bundle {
    bundleName project.name
}

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'com.cinchapi',
        name: 'concourse-plugin-core',
        version: concourseVersion

    testCompile 'junit:junit:4.11'
    testCompile group: 'com.cinchapi',
        name: 'concourse-ete-test-core',
        version: concourseVersion
}

Download Dependencies and Generate IDE Metadata#

1
./gradlew clean eclipse

Implement the Plugin#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package com.company.concourse.plugin.sample;

import com.cinchapi.concourse.server.plugin.Plugin;

/**
 * Sample Plugin Boilerplate
 */
public class SamplePlugin extends Plugin {

    /**
     * Construct a new instance.
     *
     * @param fromServer
     * @param fromPlugin
     */
    public SamplePlugin(String fromServer,
            String fromPlugin) {
        super(fromServer, fromPlugin);
    }

}

Any instance methods added to the plugin class are dynamically added to the Concourse API and can be invoked by clients.

Create the Plugin Package#

1
./gradlew bundleZip

Check the build/distributions directory for the generated .zip plugin package.

Accessing Concourse within a Plugin#

Concourse plugins have privileged access to the database using a special API. To interact with Concourse, use the runtime method inside the plugin class.

1
2
3
4
5
6
7
8
9
public void sampleWriteMethod(String key,
        Object value) {
    runtime.addKeyValue(key, value);
}

public Object sampleReadMethod(String key,
        long record) {
    return runtime.getKeyRecord(key, record);
}

If you need to access Concourse from a class that does not extend Plugin (e.g., a utility class), interact with the runtime by calling PluginRuntime.getRuntime().

Plugin Storage#

Plugins can use persistent storage that survives installs, upgrades, and uninstalls. Access storage via the storage() method:

1
2
3
4
5
6
7
public void persistData(String key, Object value) {
    storage().set(key, value);
}

public Object loadData(String key) {
    return storage().get(key);
}

Plugin storage is environment-scoped, meaning each environment gets its own isolated storage directory. Storage is located at:

1
{plugin_data_directory}/{bundle_name}/{environment}/

The plugin_data_directory is configured in concourse.yaml (default: ~/concourse/plugin-data).

Deprecated storage APIs#

Older plugins used the per-request storage methods on PluginStateContainer:

Deprecated method Replacement
data() storage()
localStorage() storage()
localStorage(String) storage() scoped to a sub-key
memoryStorage() Use an in-memory map held on the plugin instance
tempStorage() Create a Bucket directly with a temporary path

These methods still work but may be removed in a future major release. New plugins should use storage() exclusively and build any transient or in-memory state on ordinary JVM objects owned by the plugin instance.

Background Threads#

Plugins run in a separate JVM from the server, so they can safely perform long-running operations without affecting server performance.

Each plugin has access to the host Concourse instance for background processing.

Cross-Version Compatibility#

Plugins compiled against one version of Java can run on servers using a different version. For example, a plugin compiled with Java 21 can work with a server running Java 8 when using the external bootstrapper (the default).

Custom Java Runtime#

You can specify a custom Java runtime for a plugin in its configuration:

1
2
# plugin.prefs
java_home = /path/to/jdk-21

Or specify just the binary:

1
java_binary = /path/to/jdk-21/bin/java

Plugin Lifecycle#

  1. Install: The plugin .zip package is extracted to the plugins directory.
  2. Activate: The server discovers plugin classes using the configured bootstrapper (external or internal).
  3. Launch: A separate JVM is started for the plugin with IPC channels.
  4. Run: The plugin processes requests from clients and can call back to the server.
  5. Stop: The plugin is gracefully shut down when the server stops.

If a plugin crashes, the server automatically attempts to restart it with exponential backoff (up to 5 attempts). Each retry waits longer than the previous one so that a misbehaving plugin does not thrash the system.

Permanent Failed State#

If a plugin exhausts its restart budget without a clean startup, it is moved into a permanent failed state. Subsequent invocations of its methods fail immediately with a descriptive error — the server does not continue trying to restart it, and does not hang waiting for a response. Recovering a permanently-failed plugin requires administrator intervention: fix the underlying cause and then reinstall or reactivate the plugin.

This behavior protects the server from runaway plugins while making the failure mode easy to diagnose — a permanent failure surfaces as a clear error instead of a silently-stuck invocation.

Plugin Invocation#

Clients invoke plugin methods through the standard API:

1
2
3
4
5
// Java
Object result = concourse.invokePlugin(
    "com.company.SamplePlugin",
    "methodName",
    arg1, arg2);

Plugin methods have a default timeout of 5 minutes. If a method takes longer, the invocation fails with a timeout exception.