This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
LabKey Server is a large Java web application platform for biomedical research data management. It uses a modular monolith architecture with 150+ Gradle modules, built on Spring Boot 4 / Spring Framework 7 with embedded Tomcat 11. It targets Java 25 and supports both PostgreSQL and MS SQL Server databases.
# Configure IntelliJ IDEA project files
./gradlew ijConfigure
# Select database (populates application.properties from templates)
./gradlew pickPg # PostgreSQL
./gradlew pickMssql # MS SQL Server
# Build and deploy to embedded Tomcat
./gradlew deployApp
# Build a specific module
./gradlew :server:modules:platform:core:build
# Build with a predefined module set
./gradlew -PmoduleSet=community build
# Exclude test modules for faster builds
./gradlew -PexcludeTestModules build
# Build a distribution
./gradlew -PmoduleSet=distributions :distributions:base:distUnit tests are static TestCase inner classes within production source files. They are registered via the module's getUnitTests() method and run within the server JVM.
Integration tests require a running server and database. They are registered via getIntegrationTests().
Selenium UI tests (in server/testAutomation/):
./gradlew :server:testAutomation:initProperties # Generate test.properties
./gradlew :server:testAutomation:uiTests -Psuite=DRT # Run a test suiteUI tests require a running LabKey server, a browser driver (ChromeDriver or Geckodriver) on PATH, and configured test.properties.
Each module lives under server/modules/ and contains:
module.properties— metadata includingModuleClass,SchemaVersion,SupportedDatabasesbuild.gradle— usesorg.labkey.build.moduleplugin- A module class extending
SpringModule(which extendsDefaultModuleimplementingModule)
Module lifecycle methods in order: init() → versionUpdate() → afterUpdate() → startup() → startupAfterSpringConfig() → startBackgroundThreads() → destroy()
In init(), modules register controllers via addController("name", Controller.class) and set up service implementations. Controllers follow the pattern *Controller.java.
The api module provides the core framework (Module interface, SpringModule base class, services, utilities). Other key platform modules: core (auth, security, admin), query (SQL engine), experiment, study, assay, pipeline (job processing), search, audit, visualization.
server/embedded/src/org/labkey/embedded/LabKeyServer.java — a @SpringBootApplication that configures embedded Tomcat, SSL/TLS, Content Security Policy, and Log4J2.
Dual database support (PostgreSQL and MS SQL Server). Configuration lives in server/configs/application.properties, populated by pickPg/pickMssql tasks from pg.properties/mssql.properties templates. Each module declares its SchemaVersion in module.properties and manages its own schema migrations.
Mix of JSPs, React (via @labkey/components, @labkey/premium), ExtJS, and vanilla JS. Modules with TypeScript have their own package.json and use Webpack builds (via @labkey/build). TypeScript code is linted and formatted with @labkey/eslint-config. Node.js and npm versions are pinned in gradle.properties and downloaded during build.
The path to the local copy of the @labkey/components package is located in the LABKEY_UI_COMPONENTS_HOME environment variable. The path to the local copy of @labkey/premium is located in the LABKEY_UI_PREMIUM_HOME environment variable. The local copies of the packages may contain changes related to the current branches in any of the modules that have an NPM build. For example there may be changes to the @labkey/components package that affect the package in the server/modules/platform/core module.
The distributions/ directory defines 60+ distribution configurations that select which modules to package. Distributions inherit from each other (most inherit from :distributions:base). Distribution directory names must not collide with module names.
All external library versions are centralized in gradle.properties (200+ version properties). The root build.gradle forces consistent versions across all modules via resolutionStrategy. Always consult before adding, removing, or updating a third-party dependency.
- Java Streams: Prefer
StreamAPI over traditional for-loops for collection processing. - Resources: Use try-with-resources for automatic resource management.
- Nullability: Use
org.jetbrains.annotations.NotNullandorg.jetbrains.annotations.Nullableannotations. Be explicit in public API signatures. - Logging: Use Log4J2. Name the static logger
LOG, initialized viaLogHelper.getLogger():private static final Logger LOG = LogHelper.getLogger(MyClass.class, "optional description");
- Unit tests: Create a static
TestCaseinner class extendingAssertin the same file as production code. Use JUnit 4 annotations (@Test). Register new test classes in the owning module'sgetUnitTests(). - Selenium tests: Subclass
BaseWebDriverTest. Use a@BeforeClassfor setup and overridedoCleanup()for cleanup. SeeSecurityTestas an example. - Formatting: Follow IntelliJ IDEA project settings in
.idea/codeStyles/Project.xml.
sourceCompatibility/targetCompatibility: Java 25buildFromSource: true (build modules from source vs. pulling artifacts)useLocalBuild: use locally built artifactsmoduleSet: select a predefined set of modules (e.g.,community,all,distributions)excludedModules: comma-separated list of modules to exclude
When searching for Java method usages, always include *.jsp and *.jspf files in addition to *.java. JSP files contain inline Java code and are significant callers of API methods (especially anything in JspBase).
PRs should include sections for: Rationale (why the change is needed), Related Pull Requests, and Changes (notable items).