Fluent allows you to call static Java methods as if they were object methods. For example, instead of writing:
assertContains(getHttpContent(createUrl(website, "styles.css"), 60), "img.jpg");
you would write:
website.createUrl("styles.css").getHttpContent(60).assertContains("img.jpg");
Fluent works by transforming the abstract syntax tree during compilation. If a method can't be resolved using Java's normal rules, Fluent will rewrite it as such:
object.method(params...) -> method(object, params...)
and then give it back to the compiler. Now, the compiler will look for a static method taking the object as it's first parameter. Any static methods that are in scope can be used. i.e, those you've written or imported. If you are importing them from another class, you will need to use import static
so they can be resolved. No annotations are required, and you can add extension methods to primitive types.
In the above example, the extension method signatures would be:
public static URL createUrl(Website website, String path) {}
public static String getHttpContent(URL url, int timeout) {}
public static void assertContains(String string, String string) {}
Extension methods are useful when you can't (or don't want to) add methods to a class or subclass, or you are working with an interface. Commonly, such methods are called "utility methods", but in most other programming languages, you would just call them "functions".
Fluent is invoked as a javac
compiler plugin and has no runtime dependencies. The resulting class files are identical to code written with regular static method calls.
Fluent supports JDK 9 and above.
Add the following dependency to your pom.xml
:
<dependency>
<groupId>io.github.rogerkeays</groupId>
<artifactId>fluent</artifactId>
<version>0.3.1</version>
<scope>compile</scope>
</dependency>
And configure the compiler plugin:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<compilerArgs>
<arg>-Xplugin:fluent</arg>
<arg>-J--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED</arg>
</compilerArgs>
<fork>true</fork>
...
</configuration>
</plugin>
Note, older versions of the compiler plugin use a different syntax. Refer to the Maven Compiler Plugin docs for more details. Make sure you add the <fork>true</fork>
option too.
Add the following to your build.gradle
:
dependencies {
compileOnly 'io.github.rogerkeays:fluent:0.3.1'
testCompileOnly 'io.github.rogerkeays:fluent:0.3.1'
}
tasks.withType( JavaCompile ) {
options.compilerArgs += [ '-Xplugin:fluent' ]
options.fork = true
options.forkOptions.jvmArgs += [ '--add-opens=java.base/java.lang=ALL-UNNAMED']
}
If your build is using an annotations processor, change the dependency tasks to:
dependencies {
annotationProcessor 'io.github.rogerkeays:fluent:0.3.1'
testAnnotationProcessor 'io.github.rogerkeays:fluent:0.3.1'
}
Download the jar, place it on your classpath, and run javac
with -Xplugin:fluent
and -J--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED
:
wget https://repo.maven.apache.org/maven2/io/github/rogerkeays/fluent/0.3.1/fluent-0.3.1.jar
javac -cp fluent-0.3.1.jar -Xplugin:fluent -J--add-opens=java.base/java.lang=ALL-UNNAMED Test.java
Run your code like you always have:
java Test
Fluent is built using a POSIX shell script:
git clone https://github.com/rogerkeays/fluent.git
cd fluent
./build.sh install
Fluent is tested with the following JDKs:
- jdk-09.0.4
- jdk-10.0.2
- jdk-11.0.8
- jdk-12.0.2
- jdk-13.0.2
- jdk-14.0.2
- jdk-15.0.2
- jdk-16.0.2
- jdk-17.0.2
- jdk-18.0.2.1
- jdk-19.0.2
- jdk-20.0.1
- jdk-21 (early access)
- jdk-22 (early access)
To ensure backwards compatibility with existing code, Fluent has been used to compile and test the following open source projects:
There is currently no IDE support for Fluent. Contributions are welcome. If you cannot contribute code, please consider funding this feature on Patreon.
- you must use parentheses around numeric primitives when calling an extension method: e.g.
(0).inc()
- Fluent may not be compatible with other
javac
plugins, although it works with Lombok and Unchecked, at least. - If you are using Fluent with Unchecked, we recommend you specify the
-Xplugin:unchecked
option first, as this is how it is tested. You will also need JDK 11 or newer.
Please submit issues to the github issue tracker. Be sure to include the JDK version and build tools you are using. A snippet of the code causing the problem will help to reproduce the bug. Before submitting, please try a clean build of your project.
Language design can be a divisive topic. Some interesting threads around extension methods can be found here:
- Why doesn't Java support extension methods?
- Extension Methods on Wikipedia
- Uniform Function Call Syntax in D
- Dot Selection in Koka
- Fluent post on hackernews
- Fluent post on reddit
- Jamaica: our project's Patreon page, where you can fund further development.
- Unchecked: compiler plugin to disable checked exceptions (also part of Jamaica).
- Kotlin: a JVM language which supports extension methods out of the box.
- Lombok: the grand-daddy of
javac
hacks, with experimental support for extension methods. - Manifold: a
javac
plugin with many features, including extension methods. - racket-fluent: fluent syntax for Racket.
- More stuff you never knew you wanted.
- Fluent is not supported or endorsed by the OpenJDK team.
- The reasonable man adapts himself to the world. The unreasonable one persists in trying to adapt the world to himself. Therefore all progress depends on the unreasonable man. --George Bernard Shaw