Skip to content

Starting from scratch - Spring Boot

Note

This assumes you know how to use Spring Boot.

Start by creating a regular Spring Boot project, no modules are required.

Adding the dependencies

The only strictly necessary dependencies are the framework, the Spring support module, and, JDA:

Omit the v prefix from the version, e.g. 5.0.0-beta.18.

<dependencies>
    ...

    <dependency>
        <groupId>net.dv8tion</groupId>
        <artifactId>JDA</artifactId>
        <version>JDA_VERSION</version>
    </dependency>
    <dependency>
        <groupId>io.github.freya022</groupId>
        <artifactId>BotCommands</artifactId>
        <version>BC_VERSION</version>
    </dependency>
    <dependency>
        <groupId>io.github.freya022</groupId>
        <artifactId>BotCommands-spring</artifactId>
        <version>BC_VERSION</version>
    </dependency>
</dependencies>
repositories {
    ...
    mavenCentral()
}

dependencies {
    ...

    implementation("net.dv8tion:JDA:JDA_VERSION")
    implementation("io.github.freya022:BotCommands:BC_VERSION")
    implementation("io.github.freya022:BotCommands-spring:BC_VERSION")
}

Tip

You can also use the Spring developer tools to speed up your development cycle.

Optional - Configure logging

The Spring Boot starter should include logging, you can further configure it, in most cases this is in logback.xml, see Spring Boot logging docs.

For example, you can set the log level for the framework to debug by adding <logger name="io.github.freya022.botcommands" level="debug"/>.

Configuring your token

After getting your token from your bot's dashboard, you can put it in your Spring environment, this can be an environment variable or an untracked application.yaml in the current directory, or a config/ subdirectory, see Spring Boot external config docs.

Creating the main class

Add the package(s) of your application to the scanBasePackages value of your @SpringBootApplication.

Optional - Configuring the framework

Configuration of the framework is then done either by using application properties (with the prefix being either botcommands or jda), or by implementing configurers, see the BConfigurer inheritors.

Kotlin - Using a custom CoroutineEventManager

I recommend creating a custom CoroutineEventManager, that way you can configure the amount of threads or their names, which may be convenient in logs.

You can do so by implementing a ICoroutineEventManagerSupplier service, with the help of namedDefaultScope:

@BService
class CoroutineEventManagerSupplier : ICoroutineEventManagerSupplier {
    override fun get(): CoroutineEventManager {
        val scope = namedDefaultScope("WikiBot Coroutine", corePoolSize = 4)
        return CoroutineEventManager(scope)
    }
}

Creating a JDAService

Now if you try to start your bot, you will see an error about requesting a JDAService instance, this is a service which is responsible for providing (part of) the configuration of your bot, you must also start your JDA instance in createJDA, let's implement it!

What is it useful for?
  • For the framework to receive all the events, useful for command updates, uploading application emojis and more
  • To start the bot when everything is ready
  • To check if event listeners have the required gateway intents/cache flags for them to be fired

Note

The Spring support module will also check that the gateway intents and cache flags match those configured in JDAService, so, you must put them in your environment, you will then be able to set your gateway intents and cache flags using the values provided by JDAConfiguration.

@Service
class SpringBot(
    private val jdaConfiguration: JDAConfiguration,
    @Value("\${bot.token}")
    private val token: String,
) : JDAService() {
    override val intents: Set<GatewayIntent> get() = jdaConfiguration.intents

    override val cacheFlags: Set<CacheFlag> get() = jdaConfiguration.cacheFlags

    override fun createJDA(event: BReadyEvent, eventManager: IEventManager) {
        // This uses JDABuilder#createLight, with the intents and the additional cache flags set above
        // It also sets the EventManager and a special rate limiter
        light(
            token,
            activity = Activity.customStatus("In Kotlin with ❤️")
        ) {
            // Other configs
        }
    }
}
@BService
public class SpringBot extends JDAService {
    private final JDAConfiguration jdaConfiguration;
    private final String token;

    public SpringBot(JDAConfiguration jdaConfiguration, @Value("${bot.token}") String token) {
        this.jdaConfiguration = jdaConfiguration;
        this.token = token;
    }

    @NotNull
    @Override
    public Set<CacheFlag> getCacheFlags() {
        return jdaConfiguration.getCacheFlags();
    }

    @NotNull
    @Override
    public Set<GatewayIntent> getIntents() {
        return jdaConfiguration.getIntents();
    }

    @Override
    public void createJDA(@NotNull BReadyEvent event, @NotNull IEventManager eventManager) {
        // This uses JDABuilder#createLight, with the intents and the additional cache flags set above
        // It also sets the EventManager and a special rate limiter
        createLight(token)
                .setActivity(Activity.customStatus("In Java with ❤️"))
                .build();
    }
}

You can now run your bot! You should be able to run the help command, by mentioning your bot @YourBot help.

Optional - Add stacktrace-decoroutinator

I recommend adding stacktrace-decoroutinator, which will help you get clearer stacktrace when using Kotlin coroutines.

Note

Java users also benefit from it as it may help debug framework issues.

<dependencies>
    ...

    <dependency>
        <groupId>dev.reformator.stacktracedecoroutinator</groupId>
        <artifactId>stacktrace-decoroutinator-jvm</artifactId>
        <version>SD_VERSION</version>
    </dependency>
</dependencies>
repositories {
    ...
    mavenCentral()
}

dependencies {
    ...

    implementation("dev.reformator.stacktracedecoroutinator:stacktrace-decoroutinator-jvm:SD_VERSION")
}

Finally, load it on the first lines of your main program:

// stacktrace-decoroutinator has issues when reloading with hotswap agent
if ("-XX:+AllowEnhancedClassRedefinition" in ManagementFactory.getRuntimeMXBean().inputArguments) {
    logger.info { "Skipping stacktrace-decoroutinator as enhanced hotswap is active" }
} else if ("--no-decoroutinator" in args) {
    logger.info { "Skipping stacktrace-decoroutinator as --no-decoroutinator is specified" }
} else {
    DecoroutinatorRuntime.load()
}

Warning

stacktrace-decoroutinator must be loaded before any coroutine code is loaded, including suspending main functions suspend fun main() { ... }.

// stacktrace-decoroutinator has issues when reloading with hotswap agent
if (ManagementFactory.getRuntimeMXBean().getInputArguments().contains("-XX:+AllowEnhancedClassRedefinition")) {
    logger.info("Skipping stacktrace-decoroutinator as enhanced hotswap is active");
} else if (Arrays.asList(args).contains("--no-decoroutinator")) {
    logger.info("Skipping stacktrace-decoroutinator as --no-decoroutinator is specified");
} else {
    DecoroutinatorRuntime.INSTANCE.load();
}

Other resources

Take a look at other wiki pages, such as Dependency injection, Creating slash command and Using components.

Examples

You can find examples covering parts of the framework here.

Getting help

Don't hesitate to join the support server if you have any question!