This document provides essential information for AI coding agents working on the Kestra codebase.
IMPORTANT — READ FIRST
- Act as a Senior Software Engineer and Software Architect. Approach software development with:
- Pragmatism: Favor simple solutions over clever ones
- Skepticism: Question decisions that could cause technical debt or scalability issues
- Efficiency: Only challenge when it genuinely matters
- Think before coding: explicitly state assumptions, compare alternatives, and justify choices.
- Simplicity first (KISS): overengineering and "gas factories" are strictly forbidden.
- Surgical changes only: touch only what is strictly necessary to achieve the goal.
- Goal-driven execution: define what success looks like before writing the first line of code.
- Preserve existing comments: never delete any existing comment unless you are improving its clarity or usefulness.
- Write clear, maintainable, and well-documented code
- Build & test are mandatory
Monorepo built with Java (backend) and Vue (frontend), using Gradle as the build system.
- Backend: Java 25, Micronaut Framework, Lombok
- Frontend: Vue 3, TypeScript, Vite, Element Plus, Pinia
- Build: Gradle 8.x with multi-project structure (77 submodules)
- Testing: JUnit 5, Mockito, AssertJ, Vitest, Playwright
DO: Use constructor injection with final fields.
@Singleton
public class MyService {
private final SomeDependency dependency;
@Inject
public MyService(SomeDependency dependency) {
this.dependency = Objects.requireNonNull(dependency);
}
}DON'T: Use field injection (@Inject on fields directly). Always prefer constructor injection.
// 1. Package declaration and imports
// 2. Class-level annotations (@Slf4j, @Singleton, etc.)
// 3. Class declaration with Javadoc
// 4. Static constants (UPPER_SNAKE_CASE)
// 5. Injected fields (@Inject)
// 6. Constructors
// 7. Public methods
// 8. Protected methods
// 9. Private methods
// 10. Inner classes/records- Micronaut:
@Singleton,@Inject,@Controller,@Replaces,@Requires - Validation:
@Valid,@NotNull,@Nullable - Lombok:
@Slf4j,@Getter,@NoArgsConstructor,@AllArgsConstructor - Use
@Builderfor complex object creation
DO:
- Use specific exception types — extend
KestraExceptionorKestraRuntimeException - Use
Optional<T>for potentially absent returned values - Return empty collections (e.g.,
List.of(),Collections.emptyList()) for absent values - Use try-with-resources for resource management
- Log errors before re-throwing:
log.error("message", exception)
DON'T: Use generic Exception. Don't return null for collections.
- Use java records for simple data carriers
- Follow Java naming-convention best practices for Classes, Methods, Variables, Constants.
- Boolean methods: Start with
is,has,should,can(e.g.,isReadOnly()).
- Use 4-space indentation (configured in .editorconfig)
- UTF-8 encoding with LF line endings
- No trailing whitespace
- Mark utility classes as
finalwith a private constructor - Use static methods only
- Use existing utility classes (e.g.,
ListUtils,MapUtils) instead of creating new ones (io.kestra.core.utils.*)
- Use enums for fixed sets of constants
- Use
@JsonValuefor custom serialization if needed - Use
UNKNOWNenum value for unknown cases in deserialization - Compare Constants From The Left (a.k.a., Yoda conditions)
- Use a static
fromStringmethod for case-insensitive lookups usingEnumsclass.
e.g.:
public enum MyEnum {
VALUE_ONE,
VALUE_TWO,
UNKNOWN;
@JsonCreator
public static ResourceType fromString(final String value) {
return Enums.getForNameIgnoreCase(value, MyEnum.class, UNKNOWN);
}
}- Javadoc for all public classes and methods - be concise
- Use
@param,@return,@throwsappropriately - Use
{@inheritDoc}for inherited methods - Include usage examples for complex methods
- Put classes used by only controllers in the webserver module (not core)
- No business code/rule inside controllers - instead use a Service class
- All APIs must return a valid JSON object
- APIs should not return a response being a JSON array which cannot be evolved in a backwards-compatible way
- Unit tests must assert that a user can only access a given API if authorized to do so, and that access is denied otherwise
- APIs must be documented with OpenAPI annotations
- Use DTOs for requests/responses
- Always validate input parameters with
@Valid - Use
@ExecuteOn(TaskExecutors.IO)for blocking operations - Return meaningful error responses in controllers
- Never depend on repositories for code called by the workers - instead use MetaStore/StateStore facades
DO:
- Place tests in same package structure as source code
- Simple unit test with mocks over complex integration tests when possible
- Add // Given-When-Then comments for clarity
- Always use naming conventions for test methods (e.g.,
shouldPerformActionWhenCondition) - Use
@MicronautTestfor tests that require Micronaut beans - Use
@KestraTestfor tests that require running Kestra services (e.g., Executor, Scheduler)
@KestraTest
class ServiceTest {
@Inject
private ServiceClass service;
@Test
void shouldPerformActionWhenCondition() {
// Given (setup)
// When (action)
// Then (assertions)
assertThat(result).isNotNull();
}
}DON'T: Use Nested classes for test organization. Avoid complex test hierarchies.
Assertions:
- Use AssertJ:
assertThat().isEqualTo(),assertThat().isNotNull(),assertThatThrownBy(),assertThatObject() - Prefer descriptive assertion methods
- Use
@MockBeanfor mocking dependencies
Test Categories:
- Unit tests: Fast, isolated, no external dependencies
- Integration tests: Test component interaction, use
@Tag("integration") - Flaky tests: Use
@Tag("flaky")for unreliable tests
- Unit tests with Vitest and
@vue/test-utils - E2E tests with Playwright
- Storybook component tests
- Use JSdom environment for DOM testing
File Organization:
- Use 2-space indentation for Vue, JSON, YAML, CSS
- Use 4-space indentation for JavaScript/TypeScript
- Follow Vue 3 Composition API patterns
- Organize imports: Vue/framework → third-party → local modules
Naming Conventions:
- Components:
PascalCasefiles (e.g.,MyComponent.vue) - Variables/functions:
camelCase - Constants:
UPPER_SNAKE_CASE - CSS classes: Follow Element Plus conventions
TypeScript:
- Use strict TypeScript configuration
- Prefer type definitions over
any - Use interfaces for object shapes
- Use enums for fixed sets of values
# Clean build
./gradlew clean
# Full build (includes tests)
./gradlew build
# Build without tests (faster)
./gradlew build -x test -x integrationTest -x testCodeCoverageReport --refresh-dependencies --no-daemon --parallel# Run all tests (excludes flaky tests)
./gradlew test
# Run only unit tests (fastest)
./gradlew unitTest
# Run integration tests
./gradlew integrationTest
# Run flaky tests (separate from build)
./gradlew flakyTest
# Run tests for specific module
./gradlew :core:test
# Run single test class
./gradlew :module-name:test --tests "ClassName"
# Run single test method
./gradlew :module-name:test --tests "ClassName.methodName"cd ui
# Install dependencies
npm install
# Development server
npm run dev
# Type checking
npm run check:types
# Build for production
npm run build
# Run tests
npm run test:all # All tests with coverage
npm run test:unit # Unit tests only
npm run test:storybook # Storybook tests
npm run test:e2e # End-to-end tests
# Linting
npm run lint # Fix linting issues
npm run test:lint # Check linting only
# Storybook
npm run storybook # Development
npm run build-storybook # Build- Start/stop backends:
# Start databases with Docker Compose
docker compose -f docker-compose-ci.yml up
# Stop databases with Docker Compose
docker compose -f docker-compose-ci.yml down- Access application: http://localhost:8080
- Use tenant isolation for multi-tenant features
- Implement proper authorization with
@HasAnyPermission - Handle secrets securely (never log sensitive data)
- Implement pagination for large datasets
- Use streaming for large file operations
- Cache frequently accessed data appropriately
- Initialize collections with the expected size to avoid resizing overhead
Common Issues:
- Build failures: Run
./gradlew cleanand retry - Test failures: Check for service dependencies (Docker containers)
- Frontend issues: Ensure Node.js version matches package.json requirements
Debugging:
- Use IDE debugging with remote JVM debugging
- Use Micronaut's built-in health endpoints
- Enable debug logging:
--logging.level.io.kestra=DEBUG - Use JUnit and Vitest reports for test failures
Core Modules:
cli- Command Line Interfacecore- Core functionalitywebserver- Web serverui- Vue 3 frontend applicationexecutor- The component responsible for managing execution statescheduler- The component responsible for scheduling polling and schedule triggersworker- The component that executes tasks and manages worker instancesworker-controller- The component that manages worker instances and job distributionindexer- The component responsible for indexing executionsplateform- provides the Platform Bill of Materials (BOM) for dependency management
Queuing Layer:
queue- Core API for queue implementationsqueue-jdbc- JDBC-based queue implementation
Data Layer:
jdbc-*- Database implementations (H2, Postgres, MySQL)
Testing Modules:
tests- Common test utilities and base classesjmh-benchmark- JMH benchmarks for performance testing
Key Patterns:
- Repository pattern for data access
- Service layer for business logic
- Controller layer for HTTP endpoints
- Builder pattern for object construction (often with Lombok
@Builder)
- Always add tests, keep your branch rebased instead of merged, and adhere to the commit message recommendations from https://www.conventionalcommits.org/en/v1.0.0.
- Use types: chore, feat, fix, refactor, test, docs, build
- Use scopes: apps, assets, core, dashboards, deps, executions, flows, iam, namespaces, plugins, secrets, storage, scheduler, system, tasks, tenants, tests, triggers, variables, version, worker
This document should be updated as the codebase evolves. When in doubt, follow existing patterns in the codebase and maintain consistency with established conventions.