# decline: a composable command-line parser for Scala built on cats

> decline is a Typelevel library that builds command-line parsers out of composable Opts values instead of a DSL, and it targets Scala programs that already depend on cats. It fits functional Scala codebases, and it does not fit projects that need a parser for a non-Scala runtime.

**bkirwi/decline** — A composable command-line parser for Scala.

- Repository: https://github.com/bkirwi/decline
- Website: http://monovore.com/decline/
- Stars: 678 · Forks: 73
- Language: Scala
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/bkirwi-decline

## A parser made of values instead of a builder DSL

decline is a composable command-line parser for Scala, inspired by optparse-applicative and built on cats. Both halves of that sentence matter. The inspiration explains the shape of the API: a parser is a value that can be combined with other values, so the structure of your program mirrors the structure of your command line. The cats base explains why that combination is type-safe without any extra machinery, since the library leans on applicative functors that a functional Scala codebase is likely to have already.

The audience is therefore specific: Scala developers who already work in the Typelevel ecosystem. The README states that decline is a Typelevel project and follows the Typelevel Scala Code of Conduct, and the repository carries a Typelevel library badge. There is no standalone binary, no code generation step and no separate schema language. decline is a library you add to a build, and your program becomes the command-line application.

The problem it solves is a familiar one in Scala. Writing argument parsing by hand means dealing with raw string arrays, index arithmetic, type casts and error messages. A builder DSL hides the parsing behind a fluent interface but produces programs that read top-down rather than being built from independent pieces. decline takes the middle road: each option is a value with a name, a help string and a type, and the values are assembled into a final parser with combinators that any cats user already recognises.

The other benefit is that parsers become reusable. A subcommand set or a shared flag can be a value you pass around, test, and reuse, rather than a block of setup code inside one main method.

## CommandApp, Opts and the applicative that joins them

The README gives one example, and it shows the whole model. The program extends CommandApp with a name, a header and a main that returns the result of combining options:

```scala
import cats.syntax.all._
import com.monovore.decline._

object HelloWorld extends CommandApp(
  name = "hello-world",
  header = "Says hello!",
  main = {
    val userOpt =
      Opts.option[String]("target", help = "Person to greet.")
        .withDefault("world")

    val quietOpt = Opts.flag("quiet", help = "Whether to be quiet.").orFalse

    (userOpt, quietOpt).mapN { (user, quiet) =>

      if (quiet) println("...")
      else println(s"Hello $user!")
    }
  }
)
```

Several mechanisms are visible in those few lines. Opts.option[String] describes a named option that takes a value, with a help string, and withDefault turns an absent value into a fallback. Opts.flag describes a boolean switch, and orFalse turns the absence of a flag into a plain boolean so it can be used directly. The two are then combined with mapN, the applicative combinator that turns a tuple of parsers into a parser of a tuple, and the lambda receives already-typed values rather than strings.

That is the whole parsing model in miniature: describe, combine, receive typed values. The cats import is not incidental, since mapN comes from cats syntax and the whole library is expressed in those terms.

CommandApp is the other half. It supplies the program name shown in help output, the header line, and the entry point that actually parses arguments and runs the effect. Extending it is what turns a set of options into something runnable, and the class is what you would wrap in a main method in a larger application.

## Getting started: the guide at monovore.com, then a CommandApp of your own

The README does not carry a dependency snippet or an installation command, and it says so by pointing you elsewhere. It tells you to visit monovore.com/decline to get started, and that site is also listed as the project homepage. So the first step is documentation, not a package manager line.

The repository itself gives you the shape of the build. It is an sbt project, with build.sbt at the root, a version.sbt for the version, a project/ directory for build definition, and .scalafmt.conf for formatting. A contributor would add the library to a build.sbt file in the usual sbt way and follow the guide on the site for the exact coordinates and Scala version matrix.

The first real use, once the dependency resolves, is short enough to describe in full. Subclass CommandApp with a name and a header, declare the options you need as Opts values, combine them with the applicative combinators, and return the program. The README example above is that whole loop: a target option with a default and a quiet flag combined with mapN.

Two things the README does not spell out, and which you will have to find in the guide or the doc/ directory, are the supported Scala versions for the published artifact and how the library reports usage errors. The repository does contain a doc/ directory and the Scala index badge in the README points at a per-Scala-version listing, which suggests the guide is the place to check compatibility before you commit to a version.

One more practical note: because decline lives in the Typelevel ecosystem, the project page links to a Discord server, and that is where usage questions and version compatibility details are most likely to be answered.

## What lives in the repository: core, effect, refined, bench and doc

The top-level layout is more informative than the README here, and it shows that decline is a multi-module project rather than a single artifact. There is a core/ module, an effect/ module, a refined/ module, a bench/ directory for performance work, and a doc/ directory for documentation. Alongside those sit the sbt build files, the scalafmt configuration, CONTRIBUTING.md, and LICENSE.md.

The module names hint at what the library offers. An effect module implies integration with effect systems, which matters in a Typelevel codebase where the choice of effect library is a real decision. A refined module implies support for refined type checking of option values, so that a parser can demand a valid email or a positive integer rather than checking after the fact.

The presence of bench/ is worth a note. Performance comparison for a parser library is a legitimate concern, since every command-line tool pays the cost of building its parser, but there is no benchmark result in the README and no way to read one from the repository listing, so treat the claim as unverified. The directory tells you the authors cared enough to build a harness.

The repository also carries a build status badge pointing at a ci.yml workflow on the main branch, plus a Discord badge. There is a .github/ directory, so the continuous integration configuration lives there. Documentation is deliberately kept out of the README, which stays short and points at the site, so the doc/ directory and the monovore site are where the real reference documentation sits.

## Tags from 2021, commits in 2026: reading the maintenance picture

The last push to the repository was on 2026-04-08, and the project is not archived. The tagged releases, however, tell a different story. The most recent tag is v2.2.0 from 2021-12-12, described as adding Scala native support. Before that, v2.1.0 on 2021-07-05 added Scala 3 support, and v2.0.0 on the same day added Cats 3 support.

So the tags lag the source by a wide margin, and the notable milestones in the release line are all from 2021. Scala 3 support and Cats 3 support arrived together in the v2.x line, and Scala native support was the last tagged feature. If you are reading this because you need a feature, the tag list is where you would look, and the absence of tags after 2021 tells you the project is not cutting new ones on that cadence.

That pattern is common in a library that has settled: the code keeps getting fixes and platform updates on the main branch while the version numbers stay put, and downstream users pick up the artifact published for their Scala version. The Scala index badge in the README points to a listing of the artifact by Scala version, which is how you would find what is actually resolvable today.

The licence is Apache License 2.0, stated in the README and with the full text in LICENSE.md. That is a permissive licence with an explicit patent grant, which is a good fit for a library intended to sit inside other people's applications, though the details are yours to check rather than this article's to interpret.

## Where decline is the wrong tool

The first limitation is language scope. decline parses command-line arguments for Scala programs. If your CLI is a shell script, a Python tool or a Rust binary, this library does nothing for you, and the cats dependency would be foreign to that codebase.

The second is the learning curve it creates. A team that writes imperative Scala will need to understand applicative functors and the cats syntax to read the resulting code, and mapN on a tuple of options is not self-explanatory to someone who has not used that style. The tradeoff buys type-safe composition, but it is a real cost rather than a free upgrade.

The third is documentation depth in the repository itself. The README is a single example plus a link. The guide lives on an external site, and the repository README does not enumerate features, list supported Scala versions, or document error handling behaviour. If that site is unreachable or changes, the local documentation is thin. The doc/ directory exists, but its contents are not described in the repository listing.

The fourth is the release line. A team with a policy of only consuming tagged releases finds the newest tag is nearly five years old, and a team that wants a recent feature has to look at the main branch instead. Neither of those is a blocker, but both belong in the decision rather than after it.

## Conclusion

Adopt decline if your Scala codebase already uses cats and you want a parser that composes like the rest of your functional code instead of living in a separate builder DSL. Do not adopt it if your CLI lives outside Scala, if you are unwilling to learn applicative parsing, or if you need a maintained stream of tagged releases. Verify first that the version published for your Scala version actually resolves, since the newest tag is v2.2.0 from 2021-12-12 even though the last push to the main branch was on 2026-04-08, and read the guide at monovore.com/decline before assuming the README example covers your case.

## FAQ

### What is decline and what is it built on?

decline is a composable command-line parser for Scala, inspired by optparse-applicative and built on cats. It is a Typelevel project released under the Apache License 2.0.

### How do I define a command-line option with decline?

You create an Opts value, for example Opts.option[String] with a help string and a withDefault for the fallback, or Opts.flag for a switch, then combine the values with cats applicative combinators such as mapN and return the result from the main of a CommandApp.

### Which Scala versions does decline support?

The README does not list the supported versions itself, but it points to the Scala index listing for decline, which tracks the artifact by Scala version. Tagged releases include v2.1.0 with Scala 3 support and v2.2.0 with Scala native support, both from 2021.

### Is decline still being worked on and what licence does it use?

The repository is not archived and the last push was on 2026-04-08, although the newest tagged release is v2.2.0 from 2021-12-12. The project is released under the Apache License 2.0 with the full text in LICENSE.md.

## Sources

- [bkirwi/decline on GitHub](https://github.com/bkirwi/decline)
- [License: Apache-2.0](https://github.com/bkirwi/decline/blob/main/LICENSE)
- [Project website](http://monovore.com/decline/)
- [README](https://github.com/bkirwi/decline/blob/main/README.md)
- [Releases](https://github.com/bkirwi/decline/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/bkirwi-decline
