Library / SDK
yegor256/cactoos avatar
yegor256/cactoos

Cactoos: object-oriented primitives instead of static Java utilities

Object-Oriented Java primitives, as an alternative to Google Guava and Apache Commons

781 stars178 forksJavaMIT

At a glance

What is it?
Cactoos is a dependency-free Java library that wraps common JDK, Guava and Apache Commons tasks in objects rather than static methods. It fits teams that want encapsulated primitives, at the cost of noticeably more verbose call sites.
Who is it for?
Cactoos earns a place in codebases that already write object-oriented Java and want a small, dependency-free set of primitives, but it is a poor fit for a team that finds constructor-heavy call sites harder to read than static helpers.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why Cactoos replaces static utility calls with objects

Cactoos is a collection of object-oriented Java primitives, and the pitch is narrow and specific. The project states that the JDK, Guava and Apache Commons are not satisfactory because they are procedural rather than object-oriented, that they do their job mostly through static methods, and that Cactoos is suggesting to do almost exactly the same, but through objects. The audience is Java engineers who want reusable building blocks behind objects, and who dislike static imports and utility classes that hide their inputs behind argument order.

The trade-off appears in the first line of real code. A filter that elsewhere is a single static invocation becomes a constructor call with a lambda and a wrapped source, and every additional step adds another wrapper object. Nothing about Cactoos makes a program shorter. What it offers is encapsulation and a consistent vocabulary, plus a dependency-free artifact under a MIT licence that you can add without pulling a tree of transitive libraries into your build.

Adding Cactoos to a Maven or Gradle build

The project states that the library has no dependencies, so the whole installation is a single dependency declaration. For Maven projects, the coordinates are the group org.cactoos and the artifact cactoos:

xml
<dependency>
  <groupId>org.cactoos</groupId>
  <artifactId>cactoos</artifactId>
  <version>0.62.0</version>
</dependency>

Gradle users get an equivalent declaration:

groovy
dependencies {
  compile 'org.cactoos:cactoos::0.62.0
}

Two practical notes. First, Java 17 or newer is required, so a build still on an older release will not compile against it. Second, the version in the documented snippet is 0.62.0 while the most recent release for the project is 0.63.0, dated 2026-09-29, so that snippet is one release behind the latest published one. Javadoc is published alongside the artifact, so the API detail you depend on travels with the library rather than living only in the source tree.

Reading and writing text through TextOf, TeeInput and ResourceOf

The input and output examples are a short tour of the object model rather than a list of methods. Reading a text file in UTF-8 is a TextOf wrapping a File, and the value comes out through asString:

java
String text = new TextOf(
  new File("/code/a.txt")
).asString();

Writing is expressed in the same way, by combining a text source with a target through TeeInput and then measuring the result with LengthOf:

java
new LengthOf(
  new TeeInput(
    "Hello, world!",
    new File("/code/a.txt")
  )
).value();

Binary data from the classpath follows the same shape, with ResourceOf as the source and BytesOf as the accessor:

java
byte[] data = new BytesOf(
  new ResourceOf("foo/img.jpg")
).asBytes();

Notice what is missing: there is no checked exception handling in these snippets, no charset parameter and no close call. Each of those decisions belongs to a constructor you would have to look up in the Javadoc, because the examples cover the happy path only. The project also points to a separate blog post on object-oriented declarative input and output, which is where the deeper design discussion lives rather than in the library documentation itself.

Filtering, mapping and joining collections without streams

Cactoos models collection work as objects that take a source, and the project links to a write-up on lazy loading and caching through the primitives. Filtering takes a predicate and an iterable:

java
Collection<String> filtered = new ListOf<>(
  new Filtered<>(
    s -> s.length() > 4,
    new IterableOf<>("hello", "world", "dude")
  )
);

Iteration has two forms. The first wraps a mapping function and an iterable in And, and the second uses ForEach with an exec call:

java
new ForEach<>(
  input -> System.out.printf(
    "Item: %s\n", input
  )
).exec(new IterableOf<>("how", "are", "you", "?"));

Joining several iterables is done by nesting Mapped and Joined, and the examples note that a lambda capturing a non-final local variable will not compile, which is why the flattening example assigns to a local variable first. Counting elements uses LengthOf over an IterableOf, and the set builders come in two flavours, one taking varargs and one taking an existing iterable:

java
final Set<String> unique = new SetOf<>(
  "one",
  "two",
  "one",
  "three"
);

One naming trap is worth flagging early. There is a Sorted for iterables and a separate org.cactoos.set.Sorted for sets, and the examples reach for the fully qualified name to disambiguate them.

Replacing foreach and while loops with And and Endless

The functions and procedures examples show the library's central claim in practice. A traditional loop prints a greeting for each name:

java
for (String name : names) {
  System.out.printf("Hello, %s!\n", name);
}

The object-oriented equivalent is an And over a lambda and the collection, where the lambda returns a True to signal that iteration should continue:

java
new And(
  n -> {
    System.out.printf("Hello, %s!\n", n);
    return new True().value();
  },
  names
).value();

The same pattern replaces an endless wait loop, with Endless supplying the boolean that the loop body inspects:

java
new And(
  ready -> {
    System.out.println("Still waiting...");
    return !ready;
  },
  new Endless<>(booleanParameter)
).value();

The result reads like the original code only if you already know what And, True and Endless mean. Nothing at the call site says iterate until the predicate is false, and the return value of the lambda does double duty as both a print side effect and a continuation signal. This is the part of the API that experienced Java developers either take to immediately or find permanently confusing, and it is worth a small spike before a team commits to it across a service.

Version pinning and the compile context trap

There is an explicit warning about versions that is easy to skim past. It asks users to update the cactoos version in their own libraries, because newer versions may break backwards compatibility, and because projects with a newer version of cactoos may fail to even compile against your library, since Maven uses the newest version only for the whole compile context. In a build with several libraries that each declare a Cactoos dependency, one transitive upgrade can therefore break compilation of code that never changed.

The recent releases show that this is not a theoretical concern. Version 0.63.0, dated 2026-09-29, notes that there is no composite Proc to run several procs on one argument in org.cactoos.proc. Version 0.62.0, dated 2026-09-14, notes that UncheckedText has no Input, Path or File constructor, unlike TextOf. Version 0.61.1, dated 2026-07-29, notes that ScalarWithFallback wraps the original exception instead of rethrowing it. Each of those is an API shape change rather than a bug fix, so the upgrade cost of this library is mostly reading release notes and adjusting call sites. The project is not archived, and the last push was on 2026-10-19.

Where Cactoos is the wrong tool

The strongest case against Cactoos is that most of what it offers, the JDK already offers. The project frames the library as an alternative to the JDK, Guava and Apache Commons, which are the practical default for most teams. The real difference in approach is the one named there: those libraries expose their work through static methods, while Cactoos exposes it through objects. That difference is worth something when you want a value to carry its own behaviour, and it is pure overhead when you want to concatenate two strings in a test helper.

Other limits are visible in the examples. They cover only the happy path, so behaviour on malformed input, missing files and charset mismatches has to be confirmed in the published Javadoc rather than assumed. The lambda capture restriction noted in the flattening example means some ordinary Java refactors stop compiling. And the class naming is dense enough that the examples themselves have to fully qualify one of the two Sorted classes. If your goal is simply to shorten code, this library moves in the opposite direction. It is worth choosing when the object-oriented style is a house rule, not when the goal is fewer lines.

Editorial conclusion

Cactoos earns a place in codebases that already write object-oriented Java and want a small, dependency-free set of primitives, but it is a poor fit for a team that finds constructor-heavy call sites harder to read than static helpers. Before adopting it, check the class names you plan to use against the release notes for the exact version you pin, because several releases reshaped constructors and the project asks users to keep the version aligned across every library that depends on it.

Frequently asked questions

What is Cactoos and how do I add it to a Maven project?

Cactoos is a collection of object-oriented Java primitives, offered as an alternative to the procedural static methods of the JDK, Guava and Apache Commons. It has no dependencies, and the project gives a single dependency entry with groupId org.cactoos, artifactId cactoos and version 0.62.0, and states that Java 17 or newer is required.

How do I filter and count elements in a collection with Cactoos?

Wrap a predicate and an iterable in Filtered, then pass the result to ListOf, and use LengthOf over an IterableOf to count. The examples show both patterns, along with Mapped, Joined, Sorted and SetOf for the related collection operations.

How do I read a text file and write a file with Cactoos?

The project reads a file in UTF-8 with new TextOf(new File(...)).asString() and writes by combining a text value with a target through TeeInput, then measuring the result with LengthOf. It also reads binary classpath resources with BytesOf and ResourceOf.

Is Cactoos still being worked on, and what do upgrades involve?

The repository is not archived and the last push was on 2026-10-19, with release 0.63.0 dated 2026-09-29. Upgrades are not free, because the release notes describe API shape changes such as the missing composite Proc in org.cactoos.proc in 0.63.0 and the missing Input, Path or File constructor on UncheckedText in 0.62.0.

Does Cactoos work with Guava and Apache Commons at the same time?

The project presents Cactoos as an alternative to those libraries rather than a companion, noting that the JDK, Guava and Apache Commons are procedural and not object-oriented. It also warns that a newer cactoos version in a compile context can make projects fail to compile, so version alignment matters when other libraries depend on Cactoos too.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. yegor256/cactoos on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/yegor256-cactoos.svg)](https://hysenlabs.com/projects/yegor256-cactoos)

Was this useful?