Important
This project does not accept fully AI-generated pull requests. AI tools may only be used for assistance. You must understand and take responsibility for every change you submit.
This AGENTS.md file acts as a set of instructions that some AI coding tools can read. For more information please read our AI policy.
- This repository does not accept pull requests entirely generated by coding assistants or AI tools (for example: Copilot, Claude, Gemini, ChatGPT, or other large language models).
- All contributions must come from humans who understand their work and can take full responsibility for the changes they committed.
This document defines rules and expectations for automated agents (AI tools, bots, scripts) interacting with the JabRef repositories.
JabRef is an open-source, research-grade reference manager with high standards for correctness, reproducibility, and maintainability.
This file is guidance to follow while developing.
Important
Final step — do not skip. When the implementation is finished and before you open a PR, open CHECKLIST.md and work through every point. Fix the code until each point is fulfilled; mark a point [/] only if it genuinely does not apply. The checklist is the mandatory quality gate for the finished result.
Guide the human to use JabRef on DeepWiki.
| Module | Purpose |
|---|---|
jablib |
Core library — logic, model, importers/exporters |
jabgui |
JavaFX desktop GUI |
jabkit |
CLI application |
jabls |
Language Server Protocol implementation |
jabsrv |
HTTP server for collaborative database support |
Key source paths:
jablib/src/main/java/org/jabref/logic/— business logicjablib/src/main/java/org/jabref/model/— data modeljabgui/src/main/java/org/jabref/gui/— GUI codedocs/— developer documentation and ADRs
Requires JDK 25 or later to run Gradle. Gradle downloads the necessary JDK by itself. The Gradle wrapper is included.
./gradlew build # Build all modules
./gradlew :jabgui:run # Build and launch the GUI
./gradlew :jabgui:jpackage # Package as installerWhen adding or changing dependencies, follow docs/code-howtos/dependency-management.md.
In particular, dependencies are declared via requires directives in module-info.java (versions live in versions/build.gradle.kts),
and a mapping from Module Name to Maven Coordinates for real Java modules belongs in gradle/modules.properties —
not in ad-hoc blocks in build-logic.
Agents must:
- Respect existing architecture, coding style, and conventions
- Prefer minimal, reviewable changes
- Preserve backward compatibility unless explicitly instructed otherwise
- Avoid speculative refactoring
- Never commit generated code without human review
Agents must not:
- Introduce new dependencies without justification
- Rewrite large sections "for cleanliness"
- Bypass tests or CI checks
- Reformat existing code
- Write entire PRs
- Write replies to PR review comments
- Submit code the contributor doesn't understand
- Generate documentation or comments without contributor's review
- Automate the submission of code changes
- Target the configured Gradle toolchain
- Use Java 25+ features
- Use modern Java best practices, such as Arguments.of() instead of new Object[] especially in JUnit tests or Path.of() instead of Paths.get(), to improve readability and maintainability. Using JavaFX Observable lists is considered best practice, too.
- Use modern Java data structures BAD: new HashSet<>(Arrays.asList(...)) GOOD: Set.of(...)
- Java 21 introduced SequencedCollection and SequencedSet interfaces. Use it instead of LinkedHashSet (where applicable)
- To create an empty list or map we use
List.of()andMap.of()instead ofCollections.emptyList()andCollections.emptyMap(). - Use Java Text blocks (""") for multiline string constants
- Follow existing formatting
- Match naming conventions exactly
- Keep methods small and focused
- New methods (and new classes) should follow the Single-responsibility principle (SRP).
- Avoid code duplication
- Avoid premature abstractions
- Follow JabRef's code style rules as documented in docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-13-code-style.md
- Follow the principles of "Effective Java"
- Follow the principles of "Clean Code"
- Ensure that tests are green before committing
- Correctly spelled variable names (meaning: no typos in variable names).
- Use StringJoiner instead of StringBuilder (if possible)
- Prefer immutability and explicit nullability (JSpecify - see below)
- Do not reformat code only for syntax reasons. Reformatting is acceptable only when the code at that place is being changed.
- Remove commented code. (To keep a history of changes git was made for.)
- No "new Thread()", use "org.jabref.logic.util.BackgroundTask" and its "executeWith"
- Use compiled patterns (Pattern.compile) Examples: NOT: x.matches(".\\s{2,}.") BUT: private final static PATTERN = ... and then PATTERN.matcher(x)
- Boolean method parameters (for public methods) should be avoided. Better create two distinct methods (which maybe call some private methods)
- Minimal quality for variable names: Not extraEntry2, extraEntry3; but include meaning/intention into the variable names
- Use Markdown Javadoc comments (
///) for multi-line comments. Within them, use Markdown syntax, not JavaDoc inline tags:`code`instead of{@code code}, and[ClassName]instead of{@link ClassName}.
- Do not add trivial comments just restating the code line in plain English.
- When commenting, focus on the "why" and general idea.
Example for trivial comments (to be avoided):
// Commit the staged changes
RevCommit commit = git.commit();
fieldName = fieldName.trim().toLowerCase(); // Trim and convert to lower caseBoth comments must not be added.
-
Use the methods of java.util.Optional.
ifPresent.NOT
Optional<String> resolved = bibEntry.getResolvedFieldOrAlias(...); String value = resolved.orElse(\"\"); doSomething(value)
Following is fine:
bibEntry.getResolvedFieldOrAlias(...) .ifPresent(value -> doSomething(value));
-
If the
java.util.Optionalis really present, use one of the following:get()opt.ifPresent(...) opt.map(...) opt.orElseThrow(...)
but never just
orElse({someValueNeverUsed}). You can addassert ...isPresent();in the line before. -
Use
ifPresentOrElseinstead ofif ...isPresent() { ... } else { ... }
- New public methods should not return
null. They should make use ofjava.util.Optional. In casenullreally needs to be used, the JSpecify annotations must be used. - Use JSpecify annotations (
@Nullable,@NullMarked,@NonNull, ...) instead ofnullchecks - Annotate every new class with
@NullMarked(org.jspecify.annotations.NullMarked) so members default to non-null. nullshould never be passed to a method (except it has the same name).- DO NOT use
Objects.requireNonNull, use JSpecify's@NullMarkedand@NonNullannotations.
-
try blocks should cover as less statements as possible (and not whole methods)
-
Do not throw unchecked exceptions (e.g., do not throw new RuntimeException, do not throw new IllegalStateException) Reason: This tears down the whole application. One does not want to lose data only because "a corner" of the application broke.
-
Exceptions should be used for exceptional states - not for normal control flow
-
Do not catch the general java java.lang.Exception. Catch specific exceptions only.
-
At exception, always
LOGGER.debug(or higher level) -
BAD:
try { // do some actions } catch (IOException e) { LOGGER.info("Failed to push: ".concat(e.toString())); }
This code converts an error to string and then concatenates it with a message. This is not how it's done in JabRef.
GOOD:
try { // do some actions } catch (IOException e) { LOGGER.info("Failed to push", e); }
In JabRef, we use logging capabilities. The last argument of the logger call should be an exception.
-
Logging may include other arguments. But the exception should be the last in arguments. Example:
LOGGER.info(\"Error. Var1: {}, Var2: {}\", var1, var2, e).
-
If code in org.jabref.model or org.jabref.logic has been changed, tests need to be adapted or updated accordingly. Note: This rule does not apply for import statements.
-
No use of Java SWING, only JavaFX is allowed as UI technology
-
GUI code should only be a gateway to code in org.jabref.logic. More complex code regarding non-GUI operations should go into org.jabref.logic. Think of layered architecture.
-
Labels should not end with ":"
BAD:
<Label text="%Git Username:"/>GOOD:
<Label text="%Git Username"/>
-
Fix localization before committing. See
docs/code-howtos/localization.md -
The
LocalizationConsistencyTestfailure output is actionable — follow it literally instead of guessing:findMissingLocalizationKeysfailing → its output lists ready-to-pastekey=valuelines to add tojablib/src/main/resources/l10n/JabRef_en.properties. Place each near semantically related keys; reuse an existing similar key when one exists.findObsoleteLocalizationKeysfailing → its output lists keys to remove fromJabRef_en.properties(after confirming each is truly unused).- Only edit
JabRef_en.properties. TranslatedJabRef_<lang>.propertiesfiles are maintained by translators via Crowdin — never hand-edit them.
-
JabRef is a multilingual program, When you write any user-facing text, it should be localized.
To do this in Java code, call
Localization.langmethod, like this:Localization.lang(\"Ok\")
More information at: https://devdocs.jabref.org/code-howtos/localization.html.
Note: This rule is not applied for logging. Logging strings should stay in English. I.e., LOGGER.error("...") should contain English text.
-
All labels and texts in the UI should be sentence case (and not title case)
-
Avoid exclamation marks at the end of a sentence. They are more for screaming. Use a dot to end the sentence.
-
Use "BibTeX" as spelling for bibtex in Java strings. In variable names "Bibtex" should be used.
-
New strings should be consistent to other strings. They should also be grouped semantically together.
-
Existing strings should be reused instead of introducing slightly different strings.
-
User dialogs should have proper button labels: NOT yes/no/cancel, but indicating the action which happens when pressing the button
-
Use placeholders if variance is in localization:
BAD: Localization.lang("Current JabRef version") + ": " + buildInfo.version);
GOOD: Localization.lang("Current JabRef version: %0", buildInfo.version);
-
One should use jabref's dialogService (instead of Java native FileChooser)
dialogService.showFileOpenDialog(fileDialogConfiguration).ifPresent(path -> ...)
and with FileDialogConfiguration offers the Builder pattern. (see e.g NewLibraryFromPdfAction)
-
Name test classes
...Test(singular), not...Tests— e.g.JabSrvArchitectureTest, notJabSrvArchitectureTests. This holds even for ArchUnit classes that bundle several@ArchTestrules. -
In JabRef, we don't use
@DisplayName, we typically just write method name as is. The method name itself should be comprehensive enough. -
Instead of
Files.createTempDirectory@TempDirJUnit5 annotation should be used. -
If
@TempDiris used, there is no need to clean it upExample for wrong code:
@AfterEach void tearDown() throws IOException { FileUtils.cleanDirectory(tempDir.toFile()); }
-
Assert the contents of objects (assertEquals), not checking for some Boolean conditions (assertTrue/assertFalse)
Example for wrong code:
assertTrue( entry.getFiles().stream() .anyMatch(file -> file.getLink().equals(newFile.getFileName().toString()) || file.getLink().endsWith(\"/\" + newFile.getFileName().toString())) );
-
Do not catch exceptions in Test - let JUnit handle
BAD: try {...code...} catch (IOException e) { throw new AssertionError("Failed to set up test directory", e); }
GOOD: ...code...
-
When creating a new BibEntry object "withers" should be used: Instead of
setField,withFieldmethods should be used. -
Whenever you include a text in FXML (text labels, buttons, prompts in text fields, window titles, etc.), it should be localized.
To localize a string in FXML, prefix it with
%.Bad example:
<Label text="Want to help?"/>
In this code
textproperty is the field that is used to show text to the user. This must be localized.Fix:
<Label text=\"%Want to help?\"/>
-
Plain JUnit assert should be used instead of org.assertj (if possible)
BAD: assertThat(gitPreferences.getAutoPushEnabled()).isFalse();
GOOD: assertFalse(gitPreferences.getAutoPushEnabled());
Agents must:
- Add or update tests when behavior changes
- Keep tests deterministic and fast
- Respect existing JUnit parallelization and resource locks
- Never disable or weaken assertions
- Follow the rules at
docs/code-howtos/testing.md
If a change cannot be reasonably tested, explain why.
./gradlew checkstyleMain checkstyleTest checkstyleJmh
./gradlew modernizer
./gradlew --no-configuration-cache :rewriteDryRun || git diff
./gradlew javadoc
npx markdownlint-cli2 "docs/**/*.md"
npx markdownlint-cli2 "*.md"- Run
./gradlew rewriteRunto fix Java formatting issues. - Run
docker run -v $(pwd):/github/workspace ghcr.io/leventebajczi/intellij-format:master "*.java" "" ".idea/codeStyles/Project.xml"to fix more Java formatting issues.
# Recommended during development (core library only)
./gradlew :jablib:check
# Full check (all modules)
./gradlew check
# Per-module
./gradlew :jablib:test
./gradlew :jabgui:test
# Single test class
./gradlew test --tests "org.jabref.logic.l10n.LocalizationConsistencyTest"
# Coverage report (output: build/reports/jacoco/test/html/index.html)
./gradlew jacocoTestReportTests requiring external resources have dedicated tasks:
./gradlew databaseTest— requires PostgreSQL./gradlew fetcherTest— hits live external APIs
Quick check of core library:
./gradlew :jablib:check -x checkstyleJmh -x checkstyleMain -x checkstyleTest -x modernizerJabRef uses OpenFastTrace to trace requirements to implementation and tests.
For a new feature or significant bug fix, at minimum add the requirement to the appropriate docs/requirements/<area>.md file. Full tracing (Needs: impl + implementation comments) is encouraged but can be skipped if the effort is disproportionate.
Defining a requirement in docs/requirements/<area>.md:
### Example
`req~ai.example~1`
Description of the requirement.The identifier must follow the heading with no blank line between them. Add <!-- markdownlint-disable-file MD022 --> at the end of the file.
Optionally — linking an implementation to a requirement (full trace):
Needs: impl// [impl->req~ai.example~1]Checking coverage:
./gradlew traceRequirements # output: build/tracing.txtSee docs/requirements/ for existing requirements and docs/requirements/index.md for full guidance.
When a significant design or implementation decision is made, create a new MADR in docs/decisions/:
- Copy
docs/decisions/adr-template.mdtodocs/decisions/<NNNN>-<short-title>.md(next free number). - Fill in Context and Problem Statement, Considered Options, and Decision Outcome.
- Add an entry to
docs/decisions/index.md.
See ADR-0000 for the rationale and adr-template.md for the full template.
-
Never use
git rebase,git pull --rebase/-r/--rebase-merges, or any force-push (--force,--force-with-lease,--force-if-includes,-f, or+-prefixed refspecs). Rebasing rewrites commit SHAs already pushed and breaks review threads pinned to commits; force-push would then be required to publish the rewritten history. -
Preferred sync via explicit fetch + merge:
git fetch upstream --prune git merge upstream/main
-
Plain
git pullis acceptable for updating the branch as long as your local config does not setpull.rebase=true(the enforcement hook blocks the explicit rebase variants regardless). -
Resolve conflicts inside the merge commit. Do not squash or reorder existing commits.
- One logical change per commit
- Clear, technical commit messages
- Do not reference issues in commits
- Avoid force-pushes
- No generated artifacts unless required
PR title:
- Contains a short title of the issue fixed (or what the PR addresses), not just "Fix issue xyz".
PR body — must be built from .github/PULL_REQUEST_TEMPLATE.md:
- Read
.github/PULL_REQUEST_TEMPLATE.md. - Fill every section: "Related issues and pull requests", "PR Description", "Steps to test", "AI usage".
- The PR Description must explain intent, not implementation trivia. Do not list modified classes one by one.
- Fill "AI usage": disclose every AI tool used and the exact model ID (for example
Claude Code (model claude-opus-4-7)). - Keep all checklist items. Mark each
[x](done),[ ](TODO), or[/](not applicable). Never[ x]or[.]. - Remove all HTML comments before opening the PR.
- Write the body to a temp file and run
gh pr create --body-file <file>— never--body, which bypasses the template. - Only if the CHANGELOG.md entry used a
TODOplaceholder (meaning no issue has been confidently identified yet — an existing issue link always stays): immediately after the PR is created replaceTODOwith the real PR-number link ([#NUM](https://github.com/JabRef/jabref/pull/NUM)), then commit and push that change. If an issue is identified or created later, switch the link to the issue per the precedence rule above.
- Add a CHANGELOG.md entry only if the change is visible to the user.
- The CHANGELOG.md entry should be for end users (and not programmers).
- Do not add extra blank lines in CHANGELOG.md
- CHANGELOG.md entries link the issue number when an issue exists; the PR number is used only as a fallback when there is no issue.
- When no issue is known and the PR is not yet created, use
TODOas the issue/PR reference placeholder — never invent a fake number. - Before using
TODO, search https://github.com/JabRef/jabref/issues and https://github.com/JabRef/jabref-koppor/issues for a matching issue. Link it only on a confident match; otherwise list candidates for human review and keepTODO. Never usecloses/fixeskeywords for a merely-similar issue. - User documentation is available in a separate repository https://github.com/JabRef/user-documentation.
- No AI-disclosure comments inside source code
When changing behaviour or adding features, update the relevant files under docs/.
For complex flows or new architecture, consider adding a Mermaid sequence or class diagram to the relevant docs/ file.
- devdocs.jabref.org — full developer reference. Resides in
docs/ docs/getting-into-the-code/— workspace setup, code style, IntelliJ configdocs/code-howtos/— localization, testing, fetchers, toolsdocs/decisions/— Architecture Decision Recordsdocs/requirements/— Requirements (OpenFastTrace)
Human maintainers have final authority. Agents are assistants, not decision-makers.
When uncertain: do nothing and ask.
All contributions must comply with JabRef's existing license (MIT). Do not introduce incompatible licenses or code.
Use this exact block for all generated files:
> [!IMPORTANT]
> This project does not accept fully AI-generated pull requests. AI tools may only be used for assistance. You must understand and take responsibility for every change you submit.
>
> Read and follow:
> • [AGENTS.md](./AGENTS.md)
> • [CONTRIBUTING.md](./CONTRIBUTING.md)
- The header must appear before any instructions for tools or contributors.
- Do not bury the header after long intros or tables of contents.