Skip to content

JabKit: Add file output option to cititation commands (and refactoring) - #15913

Merged
calixtus merged 46 commits into
JabRef:mainfrom
JohnnyGoodNews:feat/jabkit-cli-cititation-output
Jun 11, 2026
Merged

JabKit: Add file output option to cititation commands (and refactoring)#15913
calixtus merged 46 commits into
JabRef:mainfrom
JohnnyGoodNews:feat/jabkit-cli-cititation-output

Conversation

@JohnnyGoodNews

@JohnnyGoodNews JohnnyGoodNews commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Related issues and pull requests

Closes


None, just an adoption of the existing output format capability for the get-cited/citing-works Command in JabKit CLI.

PR Description

The added feature is a result of a discussion with @koppor in the search for a good first issue/feature for me to get a better grasp on the codebase. However I would enjoy feedback on the implementation and patterns.

The implementation adds the options for file output and file format to the Commands get-cited-works and get-citing-works.

Two main refactorings/patterns emerged for me when I struggled with unit testing and comprehension due to quite some boilerplate (if-else and try-catches) in the commands:

  1. Centralized functionality by extraction of a service (or facade) layer for import/export functionality (previously mostly clustered as public static methods in JabKit) - see ADR-61
  2. Introduction of CliExceptions and CliExceptionHandler to wrap errors and their exit codes to reduce the amount of try-catch blocks that can't really be handled anyways - see ADR-62

Intent: Better 3rd party tools integration & workflows by supporting multiple output formats. (The Impact of the feature might be still low until some concrete requirements for integration are established - read: I'm not sure how users are integrating JabKit already)

The Impact of the refactoring is a decrease of duplications and it got a bit easier to read the actual flow in the commands. Also some commands had subtly different behavior which should be better now. I was able to reduce the amount of Optional/Either-type return values in favor of plain return types and Exception in error cases (I might be biased here in my preference).

Screenshot of different output formats:
JabKit-citations-output-files

Single entry library:
single-entry-library

jabref-contrib-policy:4.2:reviewed​:ok

Steps to test

get-cited-works: ./gradlew :jabkit:run --args="get-cited-works 10.1109/ICSA59870.2024.00011 --output=get-cited-works.csv --output-format=CSV"
get-citing-works: ./gradlew :jabkit:run --args="get-citing-works 10.1109/ICSA59870.2024.00011 --output=get-citing-works.html --output-format=html"
(and check the resulting output files)

AI usage


Claude Code: (mostly to challenge my ideas for simpler solutions)

  • model claude-opus-4-6 for architecture feedback; checking code against docs/adr
  • model claude-sonnet-4-6 and gpt-codex-5.3 for test code and comparison implementations (not committed/discarded)

no AI generated code in this PR

Checklist

  • I own the copyright of the code submitted and I license it under the [MIT license](https://github.com/JabRef/jabref/blob/main/LICENSE)
  • If AI tools were used, I disclosed them in the "AI usage" section and reviewed, understood, and take full ownership of all AI-generated code
  • [/] I manually tested my changes in running JabRef (always required)
    • only tested directly calling the JabKit class with arg-vector but not with a bundled os binary (I might need help there)
  • I added JUnit tests for changes (if applicable)
  • I added screenshots in the PR description (if change is visible to the user)
  • I added a screenshot in the PR description showing a library with a single entry with me as author and as title the issue number
  • I described the change in CHANGELOG.md in a way that can be understood by the average user (if change is visible to the user)
  • I checked the [user documentation](https://docs.jabref.org/) for up to dateness and submitted a pull request to our [user documentation repository](https://github.com/JabRef/user-documentation/tree/main/en)

@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Hey @JohnnyGoodNews! 👋

Thank you for contributing to JabRef!

We have automated checks in place, based on which you will soon get feedback if any of them are failing. We also use Qodo for review assistance. It will update your pull request description with a review help and offer suggestions to improve the pull request.

After all automated checks pass, a maintainer will also review your contribution. Once that happens, you can go through their comments in the "Files changed" tab and act on them, or reply to the conversation if you have further inputs. You can read about the whole pull request process in our contribution guide.

Please ensure that your pull request is in line with our AI Usage Policy and make necessary disclosures.

Comment thread jabkit/src/main/java/org/jabref/toolkit/service/ExportService.java
Comment thread jabkit/src/main/java/org/jabref/toolkit/commands/Check.java
Comment on lines +24 to +35
public CitationFetcher getCitationFetcher(CitationFetcherType citationFetcherType) {
ChatModel chatModel = ChatModelFactory.create(cliPreferences.getAiPreferences());
return CitationFetcherType.getCitationFetcher(
citationFetcherType,
cliPreferences.getImporterPreferences(),
cliPreferences.getImportFormatPreferences(),
cliPreferences.getCitationKeyPatternPreferences(),
cliPreferences.getGrobidPreferences(),
cliPreferences.getAiPreferences(),
chatModel
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

4. Chatmodel resource leak 🐞 Bug ☼ Reliability

CitationFetcherFactory.getCitationFetcher(...) creates a ChatModel but never closes it; since
ChatModel is AutoCloseable, repeated use can leak resources such as threads or HTTP connections.
Agent Prompt
## Issue description
`CitationFetcherFactory.getCitationFetcher(...)` constructs a `ChatModel` but does not close it. `ChatModel` implements `AutoCloseable`, so this is a resource leak.

## Issue Context
Previously, commands used try-with-resources around `ChatModel`. After refactoring, the lifecycle is lost.

A robust fix is to return a handle/wrapper that implements `AutoCloseable` and closes the `ChatModel` (and any other resources) when done, and to use it with try-with-resources in `GetCitedWorks`/`GetCitingWorks`.

## Fix Focus Areas
- jabkit/src/main/java/org/jabref/toolkit/service/CitationFetcherFactory.java[24-35]
- jabkit/src/main/java/org/jabref/toolkit/commands/GetCitedWorks.java[60-80]
- jabkit/src/main/java/org/jabref/toolkit/commands/GetCitingWorks.java[60-80]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread jabkit/src/main/java/org/jabref/toolkit/service/ExportService.java
@JohnnyGoodNews

Copy link
Copy Markdown
Contributor Author

Thank you both for your feedback and time.

I addressed the mentioned issues. Should I mark those points as 'resolve conversation'? (Or is that for the reviewers)

@subhramit

Copy link
Copy Markdown
Member

Should I mark those points as 'resolve conversation'? (Or is that for the reviewers)

Yeah you can leave them as-is. If it starts getting too cluttered we'll resolve them ourselves. We keep them open until them to track changes over subsequent review iterations.

Comment thread CHANGELOG.md Outdated
- The `jabkit check` command now runs both the consistency and integrity checks when given an input file without a subcommand (e.g. `jabkit check references.bib`). [#15759](https://github.com/JabRef/jabref/pull/15759)
- We added OCR feature using OCRmyPDF to extract text from scanned PDFs and create searchable PDFs including the extracted text. [#15712](https://github.com/JabRef/jabref/pull/15712)
- We Added generic CSV export filter that exports all standard BibTeX fields [#15711](https://github.com/JabRef/jabref/issues/15711)
- We added support to the JabKit commands `get-cited-works` and `get-citing-works` to output to files in various export formats

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add link to PR.


@Override
public Integer call() {
public Integer call() throws ImportServiceException, ExportServiceException {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@calixtus This is one "pattern" to investigate. Even thow the handling of "expected" excpetional cases is now non-local, I tend to like it --> more Java'ish control flow (exceptions --> BibTeX file not parseable is rather an exception than normal flow, isn't it?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this and came to the following conclusion:

  1. Effective Java (J. Bloch) states, that Exceptions should only be thrown for exceptional states. We try to follow the design principles of EJ.
    Demonstration by a file not found example:
  2. An unexpected exception may happen if either the path given by the user was not tested on entry (robustness) or was removed during operation (bc eg. cloud connection broke midway).
  3. If the internal api recieves information, it should expect the information to be valid. File errors are then unexpected an throwing an exception is correct, if the data given was validated before.

@koppor is this correctly summarized?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this correctly summarized?

Yeah - and the consequence is that before the existance and access check should be made? -- is this "nice" to do using PicoCLI?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to chime in: PicoCLI support JSR-380 BeanValidation which is nice and reusable (and de-facto industry standard). (https://picocli.info/#_validation) The docs suggest throwing ParameterException in reaction to invalid user input.

Treating the Command classes as "driving/inbound ports" like RestControllers where Validation/Cleanup happens before the actual work is delegated to service/use-case/interaction logic seems appropriate to me.

@JohnnyGoodNews

JohnnyGoodNews commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

Open from Qodo review:

  • 1. the new Picocli options (--output, --output-format) emit hardcoded, non-localized English user-facing text
    • There should be a solution CommandLine.Option#descriptionKey. However I am not sure how to bridge that to the JabRef Localization/ResourceBundle class (and a separate issue might be appropriate).
    • Option/Parameter Localization of help description: use descriptionKey instead of description and in JabKitLauncher set commandLine.setResourceBundle(Localization.getMessages());
  • 6. Missing requirement for citation output
    • To me the feature / behavior change seems too small for a dedicated requirement (or a requirement should be formulated for all Commands that support file output)
  • 4. Chatmodel resource leak / Closable not closed
    • This is a bit difficult for me to tackle and test. Is there maybe a ShutdownHook that can be used?
    • Maybe an option is decoupling ChatModel and HttpClient/Connection and use a ConnectionPool?

@koppor

koppor commented Jun 8, 2026

Copy link
Copy Markdown
Member

. Chatmodel resource leak / Closable not closed

I would be very "naive" here:

  1. JVM shuts down and will "surely" also close this
  2. Old code missed lifecycle management, too. Thus, not on you.

Missing requirement for citation output

Very OK - no req needed.

Comment on lines +18 to +22
## Considered Options

* Use specialized Utility classes for importing/exporting/fetching with static methods
* Introduce shared JabKit services to separate Commands and logic
* Keep import/export logic and fetcher creation inside each command

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Normally we list pros and cons for every possible option, but is no blocker

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kind of the "slim" MADR format for "easier" ADRs.

@koppor

koppor commented Jun 11, 2026

Copy link
Copy Markdown
Member

THen, the only thing is to resolve the conflicts and then its good to go? I am afraid it will stall otherwise.

@calixtus

Copy link
Copy Markdown
Member

#15913 (comment)

Is there any action required? Is a File.exists check then necessary?

@JohnnyGoodNews

Copy link
Copy Markdown
Contributor Author

I will add a basic JSR-380 validation, help message localization and a unit test to ensure citation key preservation. Following that I’d like to merge if you’re fine with that. (Or should those be separate and small PRs?)

@calixtus
calixtus enabled auto-merge June 11, 2026 18:48
…/feat/jabkit-cli-cititation-output

# Conflicts:
#	jabkit/src/test/java/org/jabref/toolkit/commands/PseudonymizeTest.java
@calixtus

Copy link
Copy Markdown
Member

Move fast. Can be done in a follow up. Better to merge asap to avoid more merge conflicts.

@calixtus
calixtus added this pull request to the merge queue Jun 11, 2026
@github-actions github-actions Bot added the status: to-be-merged PRs which are accepted and should go into the merge-queue. label Jun 11, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jun 11, 2026
@calixtus
calixtus added this pull request to the merge queue Jun 11, 2026
Merged via the queue into JabRef:main with commit 2cc3fdb Jun 11, 2026
66 checks passed
Siedlerchr added a commit that referenced this pull request Jun 15, 2026
…tent-throw

* upstream/main: (35 commits)
  Add native image support for `preferences` and `pseudonymize` (#15973)
  Chore(deps): Bump dev.langchain4j:langchain4j-bom in /versions (#15983)
  Chore(deps): Bump net.java.dev.jna:jna-platform in /versions (#15982)
  Fix Git preferences reset and import (#15977)
  refactor(ai): put migrations into task and lower their logging level (#15882)
  Add native image support for convert and citationkeys generate (#15971)
  JabRef-Zotero Compatibility - B (Support other item types) (#15946)
  Enable heylogs dot-space-link-style rule and fix CHANGELOG (#15968)
  Fix reflected XSS in HTTP server HTML entry preview (#15937)
  Fail closed when PR body file is unreadable in policy-tag guard (#15949)
  New Crowdin updates (#15970)
  Allow trailing notes after CHECKLIST items in PR-body guard (#15969)
  Add native image support for check-consistency and check-integrity commands (#15959)
  JabKit: Add file output option to cititation commands (and refactoring) (#15913)
  Chore(deps): Bump com.autonomousapps:dependency-analysis-gradle-plugin (#15967)
  Chore(deps): Bump org.graalvm.buildtools:native-gradle-plugin (#15966)
  Chore(deps): Bump org.graalvm.buildtools:native-gradle-plugin (#15964)
  New translations jabref_en.properties (French) (#15960)
  Replace inline style= attributes with styleClass in FXML files (#15938)
  Switch MSC Code Utils to download csv file lazy when feature is enabled (#15883)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component: JabKit [cli] dev: code-quality Issues related to code or architecture decisions first contrib status: to-be-merged PRs which are accepted and should go into the merge-queue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants