Skip to content

KBeans

KBean is the central concept of the execution engine. KBeans are classes with declared executable methods.
There is only one KBean instance per KBean class in any given Jeka base directory.

KBean classes share the following characteristics:

  • They extend the KBean class.
  • They may declare public void methods without arguments. All these methods can be invoked from the command line.
  • They may declare public fields (also known as KBean properties). These field values can be injected from the command line.
    Additionally, they can have non-public fields annotated with @JkDoc.
  • They must provide a no-argument constructor.
  • They may override the init() method.
  • They must be instantiated by the execution engine and not by user code.

Simple Example

The following KBeans expose the hello and bye methods. The rendering can be configured through nema and uppercase attributes.

@JkDoc("Displays greeting messages")
public class Greeting extends KBean {

    public String name = "Bob";

    @JkDoc("If true, the message is shown in upper case.")
    public boolean uppercase;

    @JkDoc("Prints a hello message.")
    public void hello() {
        System.out.println(formatMessage("Hello " + name + "!"));
    }

    public void bye() {
        System.out.println(formatMessage("Goodbye " + name + "!"));
    }

    private String formatMessage(String message) {
        return uppercase ? message.toUpperCase() : message;
    }
}
To execute a method from the command line, run the following example:
jeka hello name=Alice uppercase=true
To show help for this KBean, run:
jeka greeting: --doc
This will display:
Displays greeting messages.

Fields
      name=<String>   No description.
                        Default: Bob
      uppercase       If true, the message is shown in upper case.
                        Default: false
Methods
  bye    No Description.
  hello  Prints a hello message.

Location

KBeans can exists as source code in the local project jeka-src folder, at root or any package, or as class in the Jeka classpath.

Multiple KBeans in jeka-src

Many KBeans may coexist in a single jeka-src dir. In this case, use KBean names to precise on which bean to invoke, as:

jeka greeting: hello bye other: foo
In the above example, three methods coming from 2 distinct KBean are invoked.

Classpath KBeans

Jeka bundles a collection of KBeans for building projects, creating Docker images, performing Git operations, and more.

For example, running:

jeka project: compile
will compile the source code located in the src/main/java directory, using dependencies specified in the dependencies.txt file.

To display the documentation for the project KBean, run:

jeka project: --doc

To list all available KBeans in the classpath, execute:

jeka --doc

KBeans can be added to the classpath like any third-party dependency.
This can be done by setting the jeka.inject.classpath property in the jeka.properties file as follows:

jeka.inject.classpath=dev.jeka:springboot-plugin   dev.jeka:openapi-plugin:0.11.8-1

KBeans can also be included directly in the source code using the @JkDep annotation:

import dev.jeka.core.tool.JkDep;

@JkDep("dev.jeka:springboot-plugin")
@JkDep("dev.jeka:openapi-plugin:0.11.8-1")
class Build extends KBean {
...

Additionally, KBeans can be dynamically added from the command line like this:

jeka --classpath=dev.jeka:openapi-plugin:0.11.8-1 openapi:--doc

Jeka discovers KBeans automatically by scanning the classpath.

KBean Methods

A KBean method is a specific method defined in a KBean class, designed to be executable from the command line interface. For successful recognition as a command, the method must adhere to the following criteria:

  • It must be designated as public.
  • It must be an instance method, not static or abstract.
  • It must not require any arguments upon invocation.
  • It must not return any value, as indicated by a void return type.

KBean Attributes

A KBean attribute is a public instance field of a KBean class. Its value can be injected from the command line or from a property file.
Additionally, it can be a non-public field annotated with @JkDoc.

Attributes can be annotated with @JkInjectProperty("my.prop.name") to inject the value of a property into the field.

We can also inject value using *jeka.properties

For more details on field accepted types, see the dev.jeka.core.tool.FieldInjector#parse method.

KBean attributes can also represent nested composite objects. See the example in the ProjectKBean#pack field.

Naming KBeans

To be referenced conveniently, KBeans can be identified by specific names. For any given KBean class, the accepted names are:

  1. Fully qualified class name.
  2. Uncapitalized simple class name (e.g., myBuild matches org.example.MyBuild).
  3. Uncapitalized simple class name without the KBean suffix (e.g., project matches dev.jeka.core.tool.builtin.project.ProjectKBean).

Tip

Execute jeka at the root of a project to display the KBeans available on the Jeka classpath.

Document KBeans

KBean classes, methods, and attributes can include the @JkDoc annotation to provide self-documentation.
The text from these annotations is displayed when running the command:

jeka <kbeanName>: --doc
To display documentation for the default KBean, simply run:
jeka --doc

Use the @JkDocUrl annotation to indicate that a KBean has online documentation. This URL will be displayed in the output of the --doc command.

To document your KBean, you can use the following command:

jeka <kbeanName>: --doc-md
This command generates a markdown-formatted document of the specified KBean. You can copy and paste the output directly into your online documentation.

Invoke KBeans

From the Command Line

KBean methods can be executed directly from the command line using the syntax:

jeka <kbeanName>: [methodName...] [attributeName=xxx...]

Example:

jeka project: info pack tests.fork=false pack.jarType=FAT sonarqube: run

You can call multiple methods and set multiple attributes in a single command.

From IntelliJ Jeka Plugin

The IntelliJ Jeka Plugin enables invoking KBean methods directly from the IDE, either from the code editor or the project explorer tool window.

From a Plain IDE Setup

KBean methods can also be launched or debugged in an IDE by invoking the dev.jeka.core.tool.Main method and passing the corresponding command-line arguments.

Example:
Invoking the dev.jeka.core.tool.Main method with arguments project: and compile will instantiate the ProjectKBean class and invoke its compile method.

Warning

Ensure that the main method is launched with the module directory set as the working directory.
In IntelliJ, the default working directory is the project directory, which may cause issues.

To update IntelliJ defaults:
- Navigate to Run | Edit Configurations... | Application | Working Directory
- Set the value to $MODULE_DIR$.

Default KBean

The [kbeanName] prefix is optional and defaults to:

  • The KBean specified by the jeka.kbean.default property (if this property is set).
  • If the property is not set, it defaults to the first KBean found in the jeka-src directory, sorted alphabetically by fully qualified class name.

Example

The following command:

jeka doSomething aProperty=xxxx
executes the doSomething method of the default KBean.

To explicitly reference the default KBean and avoid ambiguity, use : as the prefix.

Examples

  • jeka : --doc displays the documentation of the default KBean.
  • jeka --doc displays the overall documentation.

KBean Collaboration

There's 2 goals for making KBean collaboration:

  1. Invoke a KBean from another one
  2. Configure a KBean from another one

Invoke KBean from another one

Using @JkInject

import dev.jeka.core.tool.builtins.project.ProjectKBean;
import dev.jeka.core.tool.builtins.tooling.maven.MavenKBean;
import dev.jeka.core.tool.JkInject;

@JkDoc("A simple example to illustrate KBean concept.")
class Build extends KBean {

    @JkInject
    private ProjectKBean projectKBean;

    @JkInject
    private MavenKBean mavenKBean;

    @JkDoc("Clean, compile, test, create jar files, and publish them.")
    public void packPublish() {
        projectKBean.clean();
        projectKBean.pack();
        mavenKBean.publishLocal();
    }

}
Both ProjectKBean and MavenKBean are created and injected into the Build KBean during initialization.

Using #load and #find methods

As we saw earlier, you can dynamically retrieve a KBean using the KBean#load(Class) method.
This method forces the initialization of the KBean if it is not already present.
It is useful when you need a specific KBean only within certain methods.

@JkDoc("Compiles application to native executable")
public void createNativeExec() {
    load(NativeKBean.class).compile();
}

On the other hand, the KBean#find(Class) method returns an Optional<? extends KBean>,
which is empty if the specified KBean is not initialized.
This is helpful for performing conditional tasks based on the presence of a KBean.

import dev.jeka.core.tool.builtins.tooling.docker.DockerKBean;

public void cleanup() {
    find(DockerKBean.class).ifPresent(dockerKBean -> {
        // Do something
    });
}

Configure a Kbean from another KBean.

Wether you want to create a JeKa extension or just configure a build, the technic is the same: create a KBean and configure an existing one.

For example, to configure a build, you can create a Build class as:

class Build extends KBean {

    public boolean skipIT;

    @JkPreInit
    private static void preInit(ProjectKBean projectKBean) {
        projectKBean.tests.progress = JkTestProcessor.JkProgressStyle.PLAIN;
    }

    @JkPostInit(required = true)
    private void postInit(ProjectKBean projectKBean) {
        JkProject project = projectKBean.project;
        project.flatFacade.dependencies.compile
                .add("com.google.guava:guava:33.3.1-jre")
                .add("org.openjfx:javafx-base:21");
        project.flatFacade.dependencies.test
                .add("org.junit.jupiter:junit-jupiter:5.8.1");
        project.flatFacade
                .addTestExcludeFilterSuffixedBy("IT", skipIT);
    }

    @JkPostInit
    private void postInit(MavenKBean mavenKBean) {

        // Customize the published pom dependencies
        mavenKBean.getMavenPublication().customizeDependencies(deps -> deps
                .withTransitivity("com.google.guava:guava", JkTransitivity.RUNTIME)
                .minus("org.openjfx:javafx")
        );
    }

}
This KBean defines a Build class that customizes the project and maven KBeans.

Pre-initialize KBeans

The preInit methods are invoked before the KBean is instantiated; therefore, they must be declared as static.
These methods are applied to the target KBean immediately after it is instantiated but before it is initialized.
This means they are executed prior to the injection of properties or command-line values.

The sole purpose of preInit methods is to provide default values, which can later be overridden by properties or command-line arguments.
They should not perform further configuration, as the target KBean has not yet been fully initialized when these methods are invoked.

Post-initialize KBeans

The postInit methods are invoked only if their respective KBean is fully initialized. This occurs after its properties and command-line values have been injected, and its init() method has been executed.

The required = true attribute, means that the KBean project must be instantiated by JeKa, if not already setup.

For example, when executing jeka project: pack, the ProjectKBean will be initialized with the settings provided by command-line arguments and @project...= properties defined in the jeka.properties file. The instance will then be passed to the postInit method before invoking the pack method.

When executing jeka maven: publish, the project KBean will be implicitly loaded and configured, followed by the same process for the maven KBean, before invoking the publish method.

Lifecycle

Before Kbean methods are executed, Kbeans are configured as described in the detailed sequence:

sequenceDiagram
    participant RB as Run Base
    participant IN as Init class resolver
    participant IT as Instantiater
    participant KB as KBean
    participant PR as Pre-initializer
    participant PO as Post-initializer
    participant RK as Registered KBeans

    RB->>IN:  Discover classes to init
    RB->>PR:  Register pre-init methods
    RB->>IT:  Instantiate classes to init
    IT->>KB:  New
    IT->>RK:  Register Kbean instance
    IT->>PR:  Pre-initialise KBean
    IT->>KB:  Inject fields annotated with @JkInject
    IT->>KB:  Inject values from properties
    IT->>KB:  Inject values from Command-line
    IT->>KB:  Init()
    IT->>PO:  Register KBean post-init methods
    RB->>PO:  Post-initialize all initialized KBeans

Post initialisation.

One all the KBean has been initialized, bean already initialized or extra one can be invoked from the KBean methods.

sequenceDiagram
    participant AK as A KBean
    participant RB as Run Base
    participant IT as Instantiater
    participant KB as KBean
    participant PR as Pre-initializer
    participant PO as Post-initializer
    participant RK as Registered KBeans

    AK->>RB:  Load a KBean 
    RB->>RK:  Find if KBean is already instantiated
    RB-->>AK:  Returns Kbean instance if found
    RB->>IT:  Initialise KBean
    IT->>RK:  Register KBean
    RB->>PO:  Register KBean post-init methods
    RB->>PO:  Post-initialize all initialized KBeans
    RB-->>AK:  
classDiagram
    class JkRunBase {
        +Path baseDir
        +KBean initKBean
        +KBean defaultKBean
        +JkProperties properties
        +List dependencies

        +KBean load()
        +KBean find()
        +List getKBeans()

    }

    class KBean {
        +JkRunbase runbase
    }

    JkRunBase "1" <--> "0..*" KBean
    JkRunBase --> "0..*" BaseDir: Imported Base Dirs (multi-modules)

    note for JkRunBase "There is only one JkRunBase per base folder.<br/>The base folder is the project root.<br/>In multi-module projects, usually one JkRunBase exists per module."
    note for BaseDir "This class doesn’t exist. It represents the base directory <br/>of another runbase in a multi-module project."

Multi-Project setup

In multi-project scenarios, it is common for a KBean in one project to access a KBean instance from another project. This can be achieved in a statically typed manner:

  1. In the master KBean, declare a field of type KBean (e.g., KBean importedBuild;). This field does not need to be public.
  2. Annotate the field, by specifying the relative path of the imported project (e.g., @JkInjectRunbase("../anotherModule")).
  3. Run the command jeka intellij: iml or jeka eclipse: files to refresh project metadata.
  4. Change the declared field type from KBean to the concrete type of the imported KBean.
  5. The master KBean can now access the imported KBean in a type-safe manner.
  6. For an example, see this implementation.

Tip

Ensure that the imported KBean uses KBean#getBaseDir for handling file paths. This practice ensures safe execution from any working directory.

For multi-module projects, use JkInject to access sub-module KBeans.

import dev.jeka.core.tool.builtins.project.ProjectKBean;
import dev.jeka.core.tool.builtins.tooling.maven.MavenKBean;
import dev.jeka.core.tool.JkInject;

import java.util.List;

@JkDoc("A simple example to illustrate KBean concept.")
public class MasterBuild extends KBean {

    @JkInject("./foo")
    ProjectKBean fooProject;

    @JkInject("./bar")
    ProjectKBean barProject;

    @JkDoc("For all sub-modules: clean, compile, test, create jar files, and publish them.")
    public void buildAll() {
        List.of(fooProject, barProject).forEach(projectKbean -> {
            projectKbean.clean();
            projectKbean.pack();
            MavenKBean mavenKBean = projectKbean.load(MavenKBean.class);
            mavenKBean.publishLocal();
        });
    }
}
In this example, JeKa initializes KBeans from the sub-modules ./foo and ./bar, then injects them into the MasterBuild KBean.

We can create or load a KBean on the fly using the KBean#load method. This means we only need to declare one KBean per sub-module.

Another option is to inject JkRunbase and make all calls through it:

import dev.jeka.core.tool.JkRunbase;
import dev.jeka.core.tool.builtins.project.ProjectKBean;
import dev.jeka.core.tool.builtins.tooling.maven.MavenKBean;
import dev.jeka.core.tool.JkInject;

import java.util.List;

@JkDoc("A simple example to illustrate KBean concept.")
public class MasterBuild extends KBean {

    @JkInject("./foo")
    JkRunbase foo;

    @JkInject("./bar")
    JkRunbase bar;

    @JkDoc("For all sun-modules: clean, compile, test, create jar files, and publish them.")
    public void buildAll() {
        List.of(foo, bar).forEach(runbase -> {
            ProjectKBean projectKBean = runbase.load(ProjectKBean.class);
            projectKbean.clean();
            projectKbean.pack();
            MavenKBean mavenKBean = runbase.load(MavenKBean.class);
            mavenKBean.publishLocal();
        });
    }
}

For larger sub-project structures, use KBean#getImportedKBeans() to list all sub-modules, either recursively or not.

@JkDoc("For all sub-modules: compile, test, create jar files, and publish them.")
public void buildAll() {
    this.getImportedKBeans().get(ProjectKBean.class, true).forEach(ProjectKBean::pack);
    this.getImportedKBeans().get(MavenKBean.class, true).forEach(MavenKBean::publish);
}