Creating Add-ons

In this section, we describe how to create custom add-ons and manage dependencies between them.

An add-on can be easily created using the Single Module Add-on template of the Studio project creation wizard. The resulting project will contain two source code modules: a functional module and a starter module.

By default, the functional module will contain dependencies on the Jmix data access and UI subsystems. You should create all functionality in this module.

The starter module contains Spring Boot auto-configuration for your add-on. To use the add-on in an application or in another add-on, you will add dependency on the starter module artefact to the build.gradle file of the target project, for example:

dependencies {
    implementation 'com.company.sample:sample-starter:0.0.1'

The functional module will be included transitively through the starter.

Hierarchy of Add-ons

In any Jmix application, all included core subsystems and add-ons form a hierarchy of dependencies. The hierarchy is very important for some framework features.

For example, when the application starts, it can execute Liquibase changelogs for creating or updating the database. The changelogs from different subsystems must be executed in a certain order, because if entities of add-on A are referenced from add-on B, then tables of add-on A must be created before tables of add-on B.

If some base feature supports override, the hierarchy enables predictable end result of the overriding. For example, if add-ons A, B and C define a value of the same application property, and C depends on B which depends on A, then you can be sure that the application which uses all these add-ons will get the value from add-on C.

The application itself always depends on all included subsystems. Therefore, Liquibase changelogs of the application project are always executed last, and a property value defined in the application overrides values coming from any add-ons.

The application is placed at the bottom of the hierarchy automatically, because it can be uniquely identified by the presence of a class annotated with @SpringBootApplication. Dependencies between add-ons need an explicit declaration which is described below.

@JmixModule

In an add-on project, the functional module contains the main configuration class annotated with @Configuration, @ComponentScan and @JmixModule. The latter annotation indicates that the module should be included in the hierarchy of Jmix modules.

@JmixModule attributes:

  • id - optional id of the module. Each module in the hierarchy must have a unique id. If this attribute is not set, the package name of the configuration class is used as the module id.

    Usually, base packages of add-ons are unique, so the id attribute is not needed. But if your add-on contains tests, the test configuration class is located in the same base package as the main configuration. So you should provide a unique id for the test module, for example by adding the .test suffix to the package name:

    package modularity.sample.base;
    // ...
    @JmixModule(id = "modularity.sample.base.test",
            dependsOn = BaseConfiguration.class)
    public class BaseTestConfiguration {
  • dependsOn - declares modules on which this module depends on. The attribute should contain an array of configuration classes of the modules.

    For example, if an add-on depends on Core, Data Access and UI subsystems, that is contains the following dependencies in build.gradle:

    dependencies {
        implementation 'io.jmix.core:jmix-core-starter'
        implementation 'io.jmix.data:jmix-eclipselink-starter'
        implementation 'io.jmix.ui:jmix-ui-starter'

    then its @JmixModule annotation should declare dependencies to EclipselinkConfiguration and UiConfiguration classes:

    @JmixModule(dependsOn = {EclipselinkConfiguration.class, UiConfiguration.class})
    public class BaseConfiguration {

    The dependency on the Core subsystem is brought transitively, you can check it if you look at the definition of the EclipselinkConfiguration or the UiConfiguration class.

When a Jmix application starts, it outputs to the log an INFO message from the io.jmix.core.JmixModulesProcessor logger with the result of topological sort of the hierarchy of modules. For example:

Using Jmix modules: [io.jmix.core, io.jmix.security, io.jmix.ui,
    io.jmix.securityui, io.jmix.data, io.jmix.eclipselink,
    io.jmix.securitydata, com.company.users, com.company.customers,
    com.company.products, io.jmix.localfs, io.jmix.uidata,
    com.company.sales]
Make sure that the application module is the last in the list, after all add-ons. If it’s not, most probably the dependencies between your add-ons are defined incorrectly, so check their @JmixModule(dependsOn) contents.

Module Properties

An add-on can provide application properties in a property file. To do this, define properties in a file located in the base package of the module, for example:

src/main/resources/modularity/sample/base/module.properties
jmix.ui.menu-config = modularity/sample/base/menu.xml

Then specify the path to the file in the @PropertySource annotation of the main configuration class:

package modularity.sample.base;
// ...
@PropertySource(name = "modularity.sample.base",
        value = "classpath:/modularity/sample/base/module.properties")
public class BaseConfiguration {
The @PropertySource annotation must have the name attribute set to the module id (which is normally equal to the base package name).

You may notice that in the example above the add-on defines the jmix.ui.menu-config, which is usually also defined in the application. So why the application value does not just override the value of the add-on? In fact it does: if you get the value of jmix.ui.menu-config from Spring’s Environment in the application, you’ll get only the value defined in the application. But using the JmixModules bean, you can get values of a particular property from all modules used in the application. For example:

@Autowired
Environment environment;
@Autowired
JmixModules jmixModules;

void checkProperties() {
    String envProp = environment.getProperty("jmix.ui.menu-config");
    // modularity/sample/ext/menu.xml

    List<String> moduleProps = jmixModules.getPropertyValues("jmix.ui.menu-config");
    // [io/jmix/ui/menu.xml, modularity/sample/base/menu.xml, modularity/sample/ext/menu.xml]
}

This approach is widely used in the framework for aggregating configuration defined in add-ons, like UI menu, shared fetch plans, REST authenticated/anonymous URLs and others.