The Java language specification does not require restrictions, so we have only technical limitations. The specification also does not provide for a specific compiled form, so even technical limitations are vague.
Lambda expressions are compiled into methods of the class file on which the body of the lambda expression is located, but this is not strictly required. In particular, a simple expression of the form foo -> bar(foo) can be compiled like methods. In addition, identical lambda expressions can be compiled using the same method. This is an optimization that is currently not being performed, and also makes debugging more difficult, but in principle it is allowed.
In addition, the smart compiler can start generating helper classes that host lambda bodies when it detects the unlikely event that this limit is reached.
In the current straightforward implementation, the maximum number of methods, i.e. 65535, affects the maximum number of possible lambda expressions, but this does not mean that we can create 65535 lambda expressions.
For example, there should be at least one (source code) method containing a lambda expression that will instantiate a functional interface. The minimum instruction size for the creation site is the only invokedynamic instruction that has five bytes¹. Since the maximum size of the method code is 65535, and at least one is required for the return command, there can be no more than 65534/5 == 13106 lambda expressions in one way, so to create more you need to place them in different ways, number of lambda expression methods available. You can get around this using nested lambda expressions, i.e. x -> y -> z , but even nesting has practical limitations .
Current compilers use naming schemes that generate unique names for each synthetic method, so they need individual constant pool entries. Therefore, having unique implementation methods, each lambda creation site will need an entry for the name, name and type, referencing the name, "MethodRef", referencing the name and type of declaration and (always the same) declaration class, method descriptor referring to "MethodRef" and invokedynamic entry, referring to the method handle. This amounts to a total of five constant pool entries per lambda expression, and since the constant pool is limited to 65,534 elements, and we need some entries for other purposes, the calculation is 65,500/5, so with the current compiler implementations the maximum number of lambda expressions is 13,100 . Assuming all of them have the same signature ...
In a practical test with javac (1.8u111), I was able to compile a class file with 13,098 lambda expressions of the same signature and even exactly 13,100 with disabling debugging characters before the "too many constants" error occurred. In this test class, I put lambda expressions in two constructors, since at least one constructor must be present in any case, and both can share a name record. I think you can not get more with the standard compiler.
If you want to remove the restriction imposed by the naming scheme, you still have to adhere to the rule according to which each method must be distinguishable, so it must differ at least from the name, name or signature from other methods. If you are trying to reach a theoretical maximum, you need to combine n different method names with m different signatures to allow n×m different methods, so for methods 65535 you need at least 256 name names and 256 signature entries. You still have unique combinations of name and type, so you will need the other four entries for lambda expressions, resulting in 16,247 possible lambda expressions. Since they are much smaller than 65535, you can deal with smaller combinations of name and type, that is, combine 128 names with 128 signatures, having more records for creation sites, that is, having 16311 possible lambda expressions. A few more if you abuse signature strings as method names (which work at the byte code level if the signatures do not contain reference types).
For (significantly) more, you should stop generating different methods for each lambda expression.
¹ which will make a valid bytecode. At the source level, lambda expressions arent expressions, so more code, for example. assignment of a variable is required.