Here is a "show me the code" approach to understanding the definition:
we shall look into OpenJDK javac for how it checks validity of classes annotated with @FunctionalInterface.
The most recent (as of July, 2022) implementation lies here:
com/sun/tools/javac/code/Types.java#L735-L791:
/**
 * Compute the function descriptor associated with a given functional interface
 */
public FunctionDescriptor findDescriptorInternal(TypeSymbol origin,
        CompoundScope membersCache) throws FunctionDescriptorLookupError {
    // ...
}
Class Restriction
if (!origin.isInterface() || (origin.flags() & ANNOTATION) != 0 || origin.isSealed()) {
    //t must be an interface
    throw failure("not.a.functional.intf", origin);
}
Pretty simple: the class must be an interface and must not be a sealed one.
Member Restriction
for (Symbol sym : membersCache.getSymbols(new DescriptorFilter(origin))) { /* ... */ }
In this loop, javac goes through the members of the origin class, using a DescriptorFilter to retrieve:
- Method members (of course)
- &&that are- abstractbut not- default,
- &&and do not overwrite methods from- Object,
- &&with their top level declaration not a- defaultone.
If there is only one method matching all the above conditions, then surely it is a valid functional interface, and any lambda will overwrite that very method.
However, if there are multiple, javac tries to merge them:
- If those methods all share the same name, related by override equivalence:
- then we filter them into a abstractscollection:if (!abstracts.stream().filter(msym -> msym.owner.isSubClass(sym.enclClass(), Types.this))
        .map(msym -> memberType(origin.type, msym))
        .anyMatch(abstractMType -> isSubSignature(abstractMType, mtype))) {
    abstracts.append(sym);
}
 Methods are filtered out if:
- their enclosing class is super class of that of another previously matched method,
- and the signature of that previously matched method is subsignature of that of this method.
 
 
- Otherwise, the functional interface is not valid.
Having collected abstracts, javac then goes to mergeDescriptors, which uses mergeAbstracts, which I will just quote from its comments:
/**
 * Merge multiple abstract methods. The preferred method is a method that is a subsignature
 * of all the other signatures and whose return type is more specific {@see MostSpecificReturnCheck}.
 * The resulting preferred method has a thrown clause that is the intersection of the merged
 * methods' clauses.
 */
public Optional<Symbol> mergeAbstracts(List<Symbol> ambiguousInOrder, Type site, boolean sigCheck) {
    // ...
}
Conclusion
- Functional interfaces must be interfaces :P , and must not be sealedor annotations.
- Methods are searched in the whole inheritance tree.
- Methods overlapping with those from Objectare ignored.
- defaultmethods are ignored, unless they are later overridden by sub-interfaces as non-default.
- Matching methods must all share the same name, related by override equivalence.
- Either there is only one method matching the above conditions, or matching methods can get "merged" by their class hierarchy, subsignature relations, return types and thrown clauses.