This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
ByteChef is an open-source, low-code API integration and workflow automation platform built on Spring Boot. It serves as both an automation solution and an embedded iPaaS (Integration Platform as a Service) for SaaS products.
All server commands should be run from the project root directory:
# Build and compile the project
./gradlew clean compileJava
# Run the server locally (requires Docker infrastructure)
cd server
docker compose -f docker-compose.dev.infra.yml up -d
cd ..
./gradlew -p server/apps/server-app bootRun
# Code formatting (must run before commits)
./gradlew spotlessApply
# Run checks and tests
./gradlew check
./gradlew test && ./gradlew testIntegration
# Generate component documentation
./gradlew generateDocumentationClient commands should be run from the client/ directory:
# Install dependencies
npm install
# Development server
npm run dev
# Code formatting
npm run format
# Linting and type checking
npm run lint
npm run typecheck
# Full check (lint + typecheck)
npm run check
# Build for production
npm run build
# Run tests
npm run test# Start PostgreSQL, Redis, and other services
cd server
docker compose -f docker-compose.dev.infra.yml up -d
# Or run everything in Docker
docker compose -f docker-compose.dev.server.yml up -d- Backend: Java 25 with Spring Boot 4.0.2
- Frontend: React 19.2 with TypeScript 5.9, Vite 7.3, TailwindCSS 3.4
- Database: PostgreSQL 15+ with Liquibase migrations
- Message Broker: Memory(default), Redis, RabbitMQ, Kafka, JMS, AMQP, AWS SQS
- Build System: Gradle 8+ with Kotlin DSL
- Code Execution: GraalVM Polyglot 25.0.1 (Java, JavaScript, Python, Ruby)
- Testing: JUnit 5, Vitest 4, Testcontainers
- Node.js: Version 20.19+ required for client development
- Additional Tools: MapStruct 1.6.3, Jackson 3.x (
tools.jacksonpackage), SpringDoc OpenAPI 3.0.0
-
atlas/- Workflow engine coreatlas-coordinator/- Orchestrates workflow executionatlas-execution/- Manages workflow execution lifecycleatlas-worker/- Task execution workersatlas-configuration/- Workflow configuration management
-
automation/- iPaaS automation implementationautomation-ai/- AI-powered automation featuresautomation-configuration/- Project and workflow configurationautomation-data-table/- Data table managementautomation-execution/- Workflow execution servicesautomation-knowledge-base/- Knowledge base integrationautomation-mcp/- MCP (Model Context Protocol) integrationautomation-task/- Task management servicesautomation-workflow/- Workflow coordination and execution
-
platform/- Core infrastructure servicesplatform-component/- Component definition and managementplatform-connection/- Connection handlingplatform-workflow/- Workflow managementplatform-scheduler/- Scheduling servicesplatform-oauth2/- OAuth2 authenticationplatform-webhook/- Webhook handlingplatform-ai/- AI integration services
-
core/- Foundational utilitiesevaluator/- Expression evaluationfile-storage/- File storage abstractionencryption/- Encryption servicesmessage/- Message broker abstraction
Components are located in server/libs/modules/components/ and follow this pattern:
- Each component has a
ComponentHandlerclass with@AutoServiceannotation - Components define actions (operations) and triggers (event initiators)
- Connection definitions handle authentication and configuration
- OpenAPI specifications are often included for API-based components
The server/ee/ directory contains microservices for distributed deployment:
EE Code Conventions:
- Use ByteChef Enterprise license header (not Apache 2.0) for all files under
server/ee/ - Add
@version eeJavadoc tag to all classes underserver/ee/ api-gateway-app/- API Gateway with routingai-copilot-app/- AI Copilot service for workflow assistanceconfig-server-app/- Spring Cloud Config serverconfiguration-app/- Configuration management serviceconnection-app/- Connection management servicecoordinator-app/- Workflow coordination serviceexecution-app/- Workflow execution servicescheduler-app/- Scheduling servicewebhook-app/- Webhook handling serviceworker-app/- Task execution workersruntime-job-app/- Runtime job execution
ByteChef includes 160+ built-in components in server/libs/modules/components/ covering CRM, project management, communication, e-commerce, cloud storage, AI/ML, databases, and custom code execution.
When working on components in server/libs/modules/components/:
- Component Definition Pattern:
@AutoService(ComponentHandler.class)
public class ExampleComponentHandler implements ComponentHandler {
private static final ComponentDefinition COMPONENT_DEFINITION = component("example")
.title("Example Component")
.connection(CONNECTION_DEFINITION)
.actions(/* actions */)
.triggers(/* triggers */);
}-
Testing Pattern:
- Component tests are in
./src/test/java/com/bytechef/component/ - Running tests auto-generates
.jsondefinition files in./src/test/resources/definition/ - Delete existing
.jsonfiles ANDbuild/resources/test/definition/before running tests to regenerate them
- Component tests are in
-
Documentation:
- Component documentation goes in
./src/main/resources/README.md - Run
./gradlew generateDocumentationto update docs
- Component documentation goes in
- Spotless: Code formatting is enforced. Run
./gradlew spotlessApplybefore commits - Checkstyle, PMD, SpotBugs: Static analysis tools are configured
- Tests: All new code should include appropriate tests
- Documentation: Update component documentation when adding features
- Configuration files are in
server/libs/config/ - SDK components are in
sdks/backend/java/ - CLI tools are in
cli/ - Documentation source is in
docs/
- Object keys must be in natural ascending (alphabetical) order in client code
- Applies to mock objects, hoisted state, test data, and component props
- ESLint
--fixdoes NOT auto-fix sort-keys - must be fixed manually - Example:
{content: 'x', id: 'y'}not{id: 'y', content: 'x'}
- Interface names must end with
IorProps(enforced by@typescript-eslint/naming-convention) - Example:
EnvironmentConfigI,BadgePropsType— notEnvironmentConfig
- Named imports must be sorted alphabetically within
{}(enforced bybytechef/sort-import-destructures) typekeyword imports sort by their name, not grouped separately- Example:
import {BoxIcon, CheckIcon, type LucideIcon, WrenchIcon} from 'lucide-react'
@typescript-eslint/no-non-null-asserted-optional-chainforbidsobj?.prop!- Instead, filter nulls first, then assert:
.filter((item) => item?.id != null).map((item) => { const id = item!.id!; ... })
useRefvariables must end withRefsuffix (enforced bybytechef/ref-name-suffix) — e.g.,fileInputRef,totalToUploadRef
- Do not use short or cryptic variable names on both the server and client sides; prefer clear, descriptive names that communicate intent.
- Do not prefix private methods with
_— use plain method names (e.g.,extractFrontmatternot_extractFrontmatter) - This applies everywhere, including arrow function parameters and loop variables.
- Examples:
// Bad const current = users.find((u) => u?.login === login); // Good const current = users.find((user) => user?.login === login);
// Bad for (Order o : orders) { ... } // Good for (Order order : orders) { ... }
- Always import icons with the
Iconsuffix:SearchIcon,DatabaseIcon,Loader2Icon - Not:
Search,Database,Loader2
- Use
twMergefromtailwind-mergefor conditional class merging - Do not use
cn()utility
- Use
fieldset(withborder-0) for semantic form grouping instead ofdiv - Use
useMemofor computed values instead of IIFEs in JSX - Prefer
||over??for JSX fallbacks (e.g.,trigger || defaultTrigger)
- Order hooks in components/custom hooks:
useState→useRef→ custom store hooks → other custom hooks → derived values/useMemo/useCallback→useEffect→return - All
useEffectcalls go last, immediately before thereturnstatement - Group multiple declarations of the same hook type consecutively (e.g., all
useRefcalls together, then.currentassignments in a separate block)
useFetchInterceptor.tsprovides centralized error handling for all fetch requests including GraphQL- GraphQL errors are automatically parsed and displayed as toast notifications
- Individual
onErrorhandlers on mutations are therefore not necessary for basic error display - Only add per-mutation
onErrorif you need custom behavior beyond the global toast (e.g., resetting form state)
- Enum values must use SCREAMING_SNAKE_CASE (e.g.,
DELETE,GET,QUERY,PATH) - Consistent with HttpMethod and other enums in
*.graphqlsfiles
- Avoid
hashCode()for generating unique identifiers (collision risk) - Prefer SHA-256 with first 8 bytes for deterministic long IDs, or UUID for true uniqueness
- Insert exactly one empty line before control statements to improve visual separation of logic:
- Applies to:
if,else if,else,for, enhancedfor,while,do { ... } while (...),switch,try/catch/finally.
- Applies to:
- Exceptions (do not add a blank line):
- At the very start of a file, class, or method/block (immediately after an opening
{). - When the control keyword directly continues the previous block on the same line (e.g.,
} else {,} catch (...) {,} finally {). - Immediately after another required blank line (avoid double blank lines).
- Very short top-of-method guard clauses may omit the blank line for brevity when they appear immediately after the method signature.
- If the automatic formatter (Spotless/Google Java Format) enforces a different layout, the formatter’s output wins.
- At the very start of a file, class, or method/block (immediately after an opening
- Example:
void process(User user) { if (user == null) { return; } for (Order order : user.getOrders()) { // ... } try { doWork(); } catch (IOException e) { handle(e); } }
- Insert exactly one empty line between a variable modification and a subsequent statement that uses that variable
- This improves readability by visually separating the setup from the usage
- Example:
// Bad document.setStatus(KnowledgeBaseDocument.STATUS_PROCESSING); knowledgeBaseDocumentService.saveKnowledgeBaseDocument(document); // Good document.setStatus(KnowledgeBaseDocument.STATUS_PROCESSING); knowledgeBaseDocumentService.saveKnowledgeBaseDocument(document);
- Do not add an empty line between the last method (or field) and the closing
}of a class
-
Do not chain method calls except where this is natural and idiomatic
-
Allowed exceptions (non-exhaustive):
- Builder patterns (including Lombok
@Builder) - Java Stream API (
stream(),map(),filter(),collect()) Optional- Query DSLs and criteria builders:
- Spring Data JPA
Specification(where(...).and(...).or(...)) - JPA Criteria API (fluent
CriteriaBuilder/Predicateconstruction) - QueryDSL (
JPAQueryFactory.select(...).from(...).where(...).orderBy(...)) - jOOQ (
dsl.select(...).from(...).where(...).orderBy(...))
- Spring Data JPA
- Reactive operators: Project Reactor
Mono/Flux(e.g.,map,flatMap,filter,onErrorResume) - HTTP client builder/request DSLs: Spring
WebClient, OkHttp - Testing/assertion DSLs: AssertJ, Mockito BDD APIs
- Logging fluent APIs: SLF4J 2.x
logger.atXxx()fluent logger - JSON builders and similar fluent APIs: Jackson
ObjectNode/ArrayNode, JSON‑PJsonObjectBuilder
- Builder patterns (including Lombok
-
Formatting rules:
- Break each chained step onto its own line for readability when there are 3+ operations or lines exceed the limit
- Keep declarative chains (queries, reactive pipelines) as one logical block; prefer one operation per line
- Avoid chaining when side effects are involved or intermediate values deserve names for clarity/debugging
const x = fn(callback)— iffncallscallbacksynchronously,xis not yet assigned insidecallback- Accessing
xinside such a callback throwsReferenceError: Cannot access 'x' before initialization - Fix: defer access to
xviasetTimeoutor store in a mutable ref before the call
SpotBugs:
- Don't use rough approximations of known constants (e.g., use
Math.PIinstead of3.14) - Always check return values of methods like
CountDownLatch.await(long, TimeUnit)- returns boolean - Use try-with-resources for
Connectionobjects to avoid resource leaks - Catch specific exceptions (
SQLException) instead of genericExceptionwhen possible
PMD:
- Use
@SuppressWarnings("PMD.UnusedFormalParameter")for interface-required but unused parameters - Don't qualify static method calls with the class name when already inside that class (e.g.,
builder()notClassName.builder())
Checkstyle:
- Test method names must be camelCase without underscores (e.g.,
testExecuteSuccessnottestExecute_Success) - Naming rule applies to ALL methods in test sources (including private helpers), not just
@Testmethods - Empty blocks are forbidden — a comment alone doesn't satisfy the
EmptyBlockrule; add an executable statement TODO:comments are forbidden (TodoCommentrule) — rewrite as plain comments describing intent, or implement the work
- Integration Test Naming: All integration test classes must end with "IntTest" suffix (e.g.,
WorkflowFacadeIntTest.java) - Spring 7 Programmatic Bean Registration:
- Use
BeanRegistrar+@Importinstead ofBeanFactoryPostProcessorfor programmatic bean registration - Resolve collection dependencies via
context.beanProvider(Class).orderedStream().toList()(replacesbeanFactory.getBeansOfType()) - Resolve named beans via
context.bean("beanName", Class)in supplier - Test
BeanRegistrarspecs by capturingConsumer<Spec<T>>withArgumentCaptor, applying to mockSpec, and verifying fluent calls
- Use
- Admin: admin@localhost.com / admin
- User: user@localhost.com / user
- Server: 8080 (main application)
- API: 9555 (backend API server)
- Client: 3000 (development server)
- PostgreSQL: 5432
- Redis: 6379
- Mailhog: 1025
- Create component directory in
server/libs/modules/components/ - Add component to
settings.gradle.kts - Implement
ComponentHandlerwith actions/triggers - Add tests and run to generate JSON definition
- Add documentation in README.md
- Run
./gradlew generateDocumentation
- Workflows are defined in JSON format
- Visual editor is available in the client application
- Workflow execution is handled by the Atlas engine
- Test workflows through the UI or API endpoints
- Use Liquibase for schema migrations
- Migration files are in
server/libs/config/liquibase-config/ - Database changes are applied automatically on startup
- After renaming migration files, delete stale copies from
build/resources/— Liquibase sees both old and new on classpath
- Create
@AutoConfigurationclass with@EnableJdbcRepositories(basePackages = "...")+@ConditionalOnBean(AbstractJdbcConfiguration.class) - Register in
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports - Add
spring-boot-autoconfiguredependency tobuild.gradle.kts
@AutoService(ComponentHandler.class)— ServiceLoader discovery, no Spring DI available@Component("name_v1_ComponentHandler")— Spring discovery, supports constructor injection (used by guardrails, RAG, chat-memory, and agent utils when Spring beans are needed)
- Add schema path to
client/codegen.tsschemaarray - Create operation
.graphqlfiles inclient/src/graphql/<domain>/ - Run
cd client && npx graphql-codegento regeneratesrc/shared/middleware/graphql.ts - Commit operations and generated file separately
Integration tests use Testcontainers to spin up real services:
# Run all integration tests
./gradlew testIntegration
# Run integration tests for a specific module
./gradlew :server:libs:platform:platform-workflow:platform-workflow-service:testIntegration
# Run with specific Docker image versions (recommended)
# Integration tests automatically use Testcontainers with PostgreSQL 15- Enable Debug Logging: Set logging level to DEBUG for specific packages in
application.yml - Use Workflow Test Mode: Test workflows in the UI with step-by-step execution
- Check Execution Logs: View workflow execution logs in the UI or database
- Inspect Variables: Use the workflow editor to inspect variable values at each step
- Test Actions Individually: Use the component test feature to test individual actions
- Client-side changes:
<ticket_number> client - <description> - Server-side changes:
<ticket_number> <description> - Example client:
2898 client - Add EnvironmentSelect dropdown to automation page headers - Example server:
2898 Add environment selection endpoint
- When committing, only stage files directly modified by the current task — do not include pre-existing unstaged changes that are unrelated
Before committing code, ensure:
# Server-side
./gradlew spotlessApply # Format code
./gradlew check # Run all checks
# Client-side
cd client
npm run format # Format code
npm run check # Run lint, typecheck, and testsByteChef includes a CLI tool for scaffolding components:
cd cli
./gradlew :cli-app:bootRun --args="component init openapi --name=my-component --openapi-path=/path/to/openapi.yaml"- Use
gh api graphqlwithresolveReviewThreadmutation to close threads programmatically - Get thread IDs via:
gh api graphql -f query='{ repository(owner: "X", name: "Y") { pullRequest(number: N) { reviewThreads(first: 20) { nodes { id isResolved path } } } }'
Dockerfilefor server applicationdocker-compose.ymlfor full stackdocker-compose.dev.infra.ymlfor development infrastructuredocker-compose.dev.server.ymlfor server-only development
- Helm charts are in
kubernetes/helm/bytechef-monolith/ - Supports both monolith and microservices deployments
- GitHub Actions workflows for build and test
- Automated component documentation generation
- Code quality checks are enforced
- Write unit tests for all business logic in service classes
- Mock external dependencies using Mockito
- Test component actions and triggers in isolation
- Aim for high code coverage (target: 80%+)
- Unit test class names must end with
Testsuffix only (NOTIntTest) — e.g.,KnowledgeBaseFileStorageTest - Drop
Implfrom test class names — test the interface contract, not the implementation detail
- All integration test classes must end with
IntTestsuffix - Use
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) - Leverage Testcontainers for real service dependencies
- Test configuration:
src/test/resources/config/application-testint.yml - Example integration test structure:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("testint")
class WorkflowServiceIntTest {
@Autowired
private WorkflowService workflowService;
@Test
void testWorkflowExecution() {
// Test implementation
}
}- Component tests auto-generate JSON definition files in
src/test/resources/definition/ - Delete existing
.jsonfiles ANDbuild/resources/test/definition/before running tests to regenerate them (classpath serves from build output) - Test both actions and triggers
- Verify connection configurations
- Test error handling and edge cases
- Use
@ExtendWith(ObjectMapperSetupExtension.class)for tests that useJsonUtils,MapUtils, orConvertUtils— do NOT manually callsetObjectMapper()in test configurations
DefinitionFactoryTestclasses useJsonFileAssert(snapshot pattern): if the JSON file is missing, it's auto-generated; if present, it's compared- When task dispatcher definition models change (new fields), delete snapshot JSON files from BOTH
src/test/resources/definition/andbuild/resources/test/definition/, then rerun tests
- EE apps (
server/ee/apps/) use remote client stubs instead of local service implementations - When adding new SPI interfaces to platform modules, create corresponding
@Component @ConditionalOnEEVersionstub classes in the relevant*-remote-clientmodule (e.g.,automation-configuration-remote-client) - Stubs throw
UnsupportedOperationException— they satisfy Spring DI; actual work is done via REST calls @ConditionalOnEEVersionrequiresbytechef.edition=eein the app's config- For lightweight EE apps (e.g.,
runtime-job-app) that can't pull in full remote client modules, use@TestConfigurationwith mock/stub beans in the integration test
- Component integration tests use
@ComponentIntTest→ComponentTestIntConfigurationinplatform-component-test-int-support ComponentTestIntConfigurationonly scanscom.bytechef.platform.component— beans from other packages (e.g.,com.bytechef.file.storage) must be manually registeredBase64FileStorageService.getType()returns"JDBC", so test propertybytechef.file-storage.provider=jdbcmust be set to match
cd client
# Run tests in watch mode during development
npm run test:watch
# Run tests with coverage
npm run test:coverage
# Run all quality checks (includes tests)
npm run check- Reset store state in
beforeEachviastore.setState({...initial...})— avoids cross-test leakage - Access store imperatively via
store.getState()for assertions (no hook needed) - Export stores (e.g.,
export const featureFlagsStore) to enable direct state manipulation in tests - Use
renderHookfrom@testing-library/reactfor hooks that wrap stores - Flush async store updates with
await act(async () => { await new Promise(r => setTimeout(r, 10)); })
- Global mock in
.vitest/setup.ts—onFeatureFlags: vi.fn(),isFeatureEnabled: vi.fn().mockReturnValue(false) onFeatureFlagsreturns() => void(unsubscribe); mock overrides must return a function:return () => {}import('posthog-js')dynamic imports resolve to the same mock; multiple synchronous calls share one Promise
- Test complete workflows through the UI
- Verify trigger activation and workflow execution
- Test with real connections to external services (in staging)
- Validate data transformations and error handling
GitHub Actions workflows automatically run:
- Server tests:
./gradlew check jacocoTestReport sonar - Client tests:
npm run check(lint + typecheck + tests) - SonarCloud analysis for code quality
- Integration tests with Testcontainers
Edit application.yml or set environment variables:
logging:
level:
com.bytechef: DEBUG
com.bytechef.platform.workflow: TRACE
org.springframework.web: DEBUGStart the server with debug enabled:
./gradlew -p server/apps/server-app bootRun --debug-jvm
# Connects on port 5005 by defaultIn IntelliJ IDEA:
- Run → Edit Configurations
- Add New Configuration → Remote JVM Debug
- Set port to 5005
- Start debugging
Workflow Execution Issues:
- Check
atlas-executionlogs for execution details - Inspect workflow JSON definition for syntax errors
- Verify component connections are properly configured
- Check if triggers are enabled and properly configured
Component Action Failures:
- Enable DEBUG logging for
com.bytechef.component - Verify input parameters match action definitions
- Check connection credentials and permissions
- Review component-specific logs in the execution logs
Database Issues:
- Check Liquibase changelog execution:
SELECT * FROM databasechangelog - Verify connection pool settings if seeing connection timeouts
- Check for transaction rollbacks in logs
- Use
spring.jpa.show-sql=trueto see SQL queries (dev only)
Authentication/Authorization:
- Check JWT token validity and expiration
- Verify user roles and permissions in database
- Review Spring Security filter chain execution
- Check CORS configuration for client-server communication
Database schema issues
- Reset database:
docker compose -f server/docker-compose.dev.infra.yml down -v - Check Liquibase logs for migration errors
- Manually run migrations:
./gradlew liquibaseUpdate
Workflow execution failures
- Check Atlas worker logs for task execution errors
- Verify component connections are active
- Check Redis connectivity for message broker
- Review component-specific documentation for required parameters
Integration test failures
- Ensure Docker is running (required for Testcontainers)
- Review test logs in
build/test-results/