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 commited.
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.
Guide the human to use JabRef on DeepWiki.
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 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 24+ 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 Obersvable 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; do not reformat unrelated code
- 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)
- Code should not be reformatted only because of syntax. There need to be new statements added if reformatting.
- 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 specific exceptions,
catch (Exception e)is a no-go - At exception, always
LOGGER.debug(or higher level) - Use Markdown Javadoc comments (
///) for multi-line comments
- 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 useget()(and notorElse(\"\")) -
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 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 shoud 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 loose 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 exeptions only.
-
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 arugment 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 layerd archicture.
-
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 -
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)
-
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"
JUnit tests can be run locally with following command:
CI=true xvfb-run --auto-servernum ./gradlew :jablib:check -x checkstyleJmh -x checkstyleMain -x checkstyleTest -x modernizer
When creating 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 descriptions:
- Must explain intent, not implementation trivia.
- AI-disclosure
- The pull request title should contain a short title of the issue fixed (or what the PR adresses) and not just "Fix issue xyz"
- The "Mandatory checks" are Markdown TODOs. They should be formatted as that. Wrong:
- [ x]. Either- [ ]or- [x].
- The CHANGELOG.md entry should be for end users (and not programmers).
- Do not add extra blank lines in CHANGELOG.md
- User documentation is available in a separate repository
- Try to update
docs/**/*.md - No AI-disclosure comments inside source code
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 be used assistively only. 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.