CodeGym /Courses /JAVA 25 SELF /Functional interfaces: @FunctionalInterface

Functional interfaces: @FunctionalInterface

JAVA 25 SELF
Level 20 , Lesson 3
Available

1. Introduction

A functional interface is an interface that contains exactly one abstract method (that is, a method without an implementation). Only such an interface can be used for a concise method implementation — in Java this is done with lambda expressions (we will study them later).

Why only one method?

Because a functional interface describes exactly one operation. If it had two or more methods, it would be unclear which method must be implemented. So the rule is simple: one interface — one abstract method.

Examples from the standard library

  • Runnable — for tasks in threads (void run())
  • Callable<V> — for tasks that return a result (V call())
  • Comparator<T> — for comparing objects (int compare(T o1, T o2))
  • Consumer<T> — a “consumer” of a value (void accept(T t))
  • Supplier<T> — a “supplier” of a value (T get())
  • Function<T, R> — a function from T to R (R apply(T t))
  • Predicate<T> — a condition check (boolean test(T t))

Here is the Runnable interface, for example:

public interface Runnable {
    void run();
}

And here is the Comparator interface:

public interface Comparator<T> {
    int compare(T o1, T o2);
    // ... there are also default and static methods, but there is only one abstract method!
}

Important: default and static methods are not considered abstract, so there can be any number of them!

2. Annotation @FunctionalInterface

Java is strict and principled. To avoid confusion, it lets you explicitly mark an interface as functional using the @FunctionalInterface annotation. It’s like a sticker: “Works with only one button!” — so nobody adds extra stuff.

@FunctionalInterface
public interface Operation {
    int apply(int a, int b);
}

Now, if you suddenly forget and add a second abstract method, the compiler will immediately report an error:

@FunctionalInterface
public interface Oops {
    void doIt();
    void doSomethingElse(); // Error! Two abstract methods
}

Is the annotation mandatory?

No, it is not required. An interface will still be functional without it if it contains exactly one abstract method. But with the annotation you clearly show your intent and protect yourself from accidental mistakes.

Can you add default and static methods?

Yes, you can! The key is to have only one abstract method. All other methods can be default or static, as many as you like.

Example:

@FunctionalInterface
public interface FancyOperation {
    int apply(int a, int b);

    default void printInfo() {
        System.out.println("I am a fancy operation!");
    }

    static void description() {
        System.out.println("A functional interface for arithmetic.");
    }
}

3. Examples of declaring and using

Suppose you want to describe an operation on two numbers. Here is how to do it:

@FunctionalInterface
public interface Operation {
    int apply(int a, int b);
}

Now you can implement this interface in different ways.

Implementation via a regular class

public class SumOperation implements Operation {
    @Override
    public int apply(int a, int b) {
        return a + b;
    }
}

Usage:

Operation sum = new SumOperation();
System.out.println(sum.apply(2, 3)); // 5

Implementation via an anonymous class

Operation multiply = new Operation() {
    @Override
    public int apply(int a, int b) {
        return a * b;
    }
};
System.out.println(multiply.apply(2, 3)); // 6

A note about lambdas

Starting with Java 8, such interfaces are conveniently implemented using lambda expressions — a more concise syntax. We will study lambdas in a couple of lectures, so for now it is enough to know that functional interfaces exist specifically to make working with them as convenient as possible.

4. Practice: write your own functional interface

Task 1. Make your own Action!

Create an Action interface that takes a string and returns nothing. Implement it via an anonymous class that prints the string in uppercase.

@FunctionalInterface
interface Action {
    void act(String s);
}

public class ActionDemo {
    public static void main(String[] args) {
        Action shout = new Action() {
            @Override
            public void act(String text) {
                System.out.println(text.toUpperCase());
            }
        };

        shout.act("i am learning java!"); // I AM LEARNING JAVA!
    }
}

(Later we will see how to write this more concisely using lambda expressions.)

Task 2. Number filter

Create a NumberPredicate interface with the method boolean test(int n). Implement an even check using an anonymous class.

@FunctionalInterface
interface NumberPredicate {
    boolean test(int n);
}

public class PredicateDemo {
    public static void main(String[] args) {
        NumberPredicate isEven = new NumberPredicate() {
            @Override
            public boolean test(int n) {
                return n % 2 == 0;
            }
        };

        System.out.println(isEven.test(4)); // true
        System.out.println(isEven.test(7)); // false
    }
}

Task 3. Use standard interfaces

Instead of your own interface, you can use the ready-made Predicate<Integer>:

import java.util.function.Predicate;

Predicate<Integer> isPositive = new Predicate<Integer>() {
    @Override
    public boolean test(Integer x) {
        return x > 0;
    }
};

System.out.println(isPositive.test(10)); // true
System.out.println(isPositive.test(-5)); // false

Table: functional interfaces from the standard library

Interface Method Description Usage example
Runnable
void run()
A task with no arguments and no result Threads, timers
Callable<V>
V call()
A task with a result Threads, ExecutorService
Comparator<T>
int compare(T, T)
Comparison of two objects Sorting collections
Consumer<T>
void accept(T)
A “consumer” of a value Iterating over a collection
Supplier<T>
T get()
A “supplier” of a value Lazy initialization, data generation
Function<T, R>
R apply(T)
A function from T to R Data transformation
Predicate<T>
boolean test(T)
Condition check Filtering collections

5. Common mistakes when working with functional interfaces

Error #1: you added a second abstract method. If an interface has more than one abstract method, it ceases to be functional. The compiler (especially with @FunctionalInterface) will immediately report an error.

Error #2: you forgot that default and static methods are not considered abstract. You can safely add them to a functional interface — this does not violate the rule of “one abstract method”.

Error #3: you implemented the method signature incorrectly. For example, the interface requires two arguments, but you wrote a method with only one. Always check the signatures.

Error #4: you don’t use @FunctionalInterface and accidentally break the interface. If you don’t annotate the interface, you might accidentally add an extra method — and then spend a long time figuring out why the code doesn’t work. It’s better to add the annotation for clarity.

1
Task
JAVA 25 SELF, level 20, lesson 3
Locked
Your Personal Notifier
Your Personal Notifier
1
Task
JAVA 25 SELF, level 20, lesson 3
Locked
Game Checker: High Scores
Game Checker: High Scores
1
Task
JAVA 25 SELF, level 20, lesson 3
Locked
Text Validator: String Length Check
Text Validator: String Length Check
1
Task
JAVA 25 SELF, level 20, lesson 3
Locked
Text Converter with Convenient Output
Text Converter with Convenient Output
Comments (1)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
Sid Level 21, United States of America, United States
21 May 2026
tasks are not up to the mark, even got introduced with Lambda expressions,