<div dir="ltr"><div>I'm sorry. I see that my prior email wraps poorly when rendered in the web interface. Here's the same email wrapped to 76 characters for better readability:</div><div><br></div><div>Title: Enhanced Void return<br>Author: Brandon Mintern<br>Organization: Everlaw<br>Created: 2025/03/27<br>Type: Feature<br>State: Draft<br>Exposure: Open<br>Component: specification / language<br>Scope: SE<br>Template: 2.0<br><br>Summary<br>-------<br><br>Allow `Void`-returning methods to omit the `return null` statement. Method<br>implementations can still `return null` if desired, but they can also use a<br>plain `return` or omit the statement altogether.<br><br>Goals<br>-----<br><br>Improve ergonomics when using methods that accept a lambda and optionally<br>return a result. Enhance the compiler to:<br><br>- Automatically insert `return null` (or convert `return`) in<br> `Void`-returning methods.<br>- Infer `Void` as the generic return type in method references and lambdas<br> without a `return`.<br><br>Non-Goals<br>---------<br><br>- `Void`-returning methods are still free to explicitly state `return null`.<br>- The scope should be limited such that:<br> - The compiler can do the heavy lifting, without any JVM changes.<br> - No new lambda objects would be constructed implicitly.<br><br>Motivation<br>----------<br><br>Lambdas provide powerful abstractions for working with closeable resources<br>or in temporary contexts. For example, we can define the following<br>abstraction:<br><br> <R> R withBufferedReader(Path path, BufferedReaderFunction<R> f) {<br> try (var reader = Files.newBufferedReader(path)) {<br> return f.apply(reader);<br> } catch (IOException e) {<br> throw new UncheckedIOException(e);<br> }<br> }<br><br> @FunctionalInterface<br> interface BufferedReaderFunction<R> {<br> R apply(BufferedReader reader) throws IOException;<br> }<br><br>Callers can use this method as follows:<br><br> var firstLine = withBufferedReader(path, r -> r.lines().findFirst());<br><br>When a caller doesn't need the return value, though, the caller must include<br>an awkward `return null`:<br><br> withBufferedReader(path, reader -> {<br> this.header = reader.readLine();<br> this.data = reader.lines().toList();<br> return null;<br> });<br><br>These examples used `BufferedReader` for familiarity, but abstractions like<br>this are arguably more useful when reading from other persistent data stores<br>(e.g., databases, search indexes) or when writing context-specific code.<br>Using a completely hypothetical `UserContextHolder`:<br><br> <R> R inUserContext(User user, Supplier<R> supplier) {<br> var oldContext = UserContextHolder.getContext();<br> UserContextHolder.setUserContext(user);<br> try {<br> return supplier.get();<br> } finally {<br> UserContextHolder.setContext(oldContext);<br> }<br> }<br><br>Then we might have system code that looks like the following:<br><br> interface Report {<br> User getUser();<br> GeneratedReport generate();<br> }<br><br> for (var report: pendingReports) {<br> save(inUserContext(report.getUser(), report::generate));<br> }<br><br> // or:<br><br> interface Job {<br> User getUser();<br> void execute();<br> }<br><br> for (var job : pendingJobs) {<br> inUserContext(job.getUser(), () -> {<br> job.execute();<br> return null;<br> });<br> // inUserContext(job.getUser(), job::execute) with this proposal<br> }<br><br>Description<br>-----------<br><br>When a method has a fully instantiated return type of `Void`, all of the<br>following would be valid:<br><br>- `return null`<br>- `return`<br>- omitted `return` (implicit `void` return)<br><br>`Void`-returning methods can continue using `return null` without warning.<br>It should be at least a warning, though, for a single method to mix `return<br>null` and any `void`-style return (implicit or explicit).<br><br>When a functional interface has a generic return type parameter, lambdas can<br>use `void`-style returns, and references to `void`-returning methods can be<br>used, where the compiler will infer the return type to be `Void` and<br>logically insert the necessary `return null` statements.<br><br>As with functional interfaces in general, an instance of one type cannot be<br>automatically converted to another. That is, a `Runnable` cannot be passed<br>as a `Supplier<T>`, inferring `T = Void`. `Runnable::run` could be used<br>instead.<br><br>When a method returns a generic type, and when that type is inferred as<br>`Void` due to the use of an *enhanced `Void` return*, it would be an error<br>to assign the result to a variable. Returning to the `inUserContext`<br>example:<br><br> var report = inUserContext(report.getUser(), report::generate); // OK<br><br> Void result = inUserContext(job.getUser(), () -> {<br> job.execute();<br> return null;<br> }); // OK: result is null (backwards compatible)<br><br> Void result = inUserContext(job.getUser(), job::execute); // ERROR<br><br> Void result = inUserContext(job.getUser(), () -> {<br> job.execute();<br> }); // ERROR<br><br>This enhancement would be implemented entirely by the compiler, generating<br>JVM bytecode as if `return null` had been used where it's currently needed.<br><br>Alternatives<br>------------<br><br>Returning to the `withBufferedReader` example, without this proposal, we can<br>instead define sibling methods (and types) to avoid the need for<br>`return null`:<br><br> void withBufferedReader(Path path, BufferedReaderProcedure f) {<br> withBufferedReader(path, reader -> {<br> f.accept(reader);<br> return null;<br> });<br> }<br><br> interface BufferedReaderProcedure {<br> void accept(BufferedReader reader) throws IOException;<br> }<br><br> withBufferedReader(path, reader -> {<br> this.header = reader.readLine();<br> this.data = reader.lines().toList();<br> });<br><br>This works, sort of, but we quickly find that it doesn't actually work.<br>Returning to the first example:<br><br> var firstLine = withBufferedReader(path, r -> r.lines().findFirst());<br><br>This now fails to compile, reporting "Ambiguous method call." We can work<br>around that issue, of course, by standardizing on verb pairs to use in these<br>scenarios:<br><br> R applyingBufferedReader(...)<br> void acceptingBufferedReader(...)<br><br>In reality, though, this disambiguation is not the biggest issue. Rather, in<br>most codebases, it would be hard to justify defining duplicate methods and<br>types. Many development teams would instead use `return null` statements<br>where necessary, avoid the abstraction in cases where `return null` is<br>needed, or avoid defining the abstraction altogether.<br><br>Risks and Assumptions<br>---------------------<br><br>Some codebases may have sibling methods like those described above, where<br>this enhancement would cause new "Ambiguous method call." when using the<br>`void`-returning method. However:<br><br>- Already-compiled code would not be affected since there is no JVM change.<br>- Impacted code would likely benefit from this enhancement, with new<br> compiler errors pointing to duplicate code that can likely be deleted.<br><br></div></div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Thu, Mar 27, 2025 at 11:59 AM Brandon Mintern <<a href="mailto:mintern@everlaw.com">mintern@everlaw.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div>Hi all,</div><div><br></div><div>I'm interested in allowing `return null` to be omitted from Void-returning methods. Apologies if I'm doing this wrong, but I've drafted the following JEP. I'd love to get feedback and discuss the feasibility of a change like this.</div><div><br></div><div>Thanks!</div><div>Brandon</div><div><br></div><div>Title: Enhanced Void return<br>Author: Brandon Mintern<br>Organization: Everlaw<br>Created: 2025/03/27<br>Type: Feature<br>State: Draft<br>Exposure: Open<br>Component: specification / language<br>Scope: SE<br>Template: 2.0<br><br>Summary<br>-------<br><br>Allow `Void`-returning methods to omit the `return null` statement. Method<br>implementations can still `return null` if desired, but they can also use a<br>plain `return` or omit the statement altogether.<br><br>Goals<br>-----<br><br>Improve ergonomics when using methods that accept a lambda and optionally return<br>a result. Enhance the compiler to:<br><br>- Automatically insert `return null` (or convert `return`) in `Void`-returning<br> methods.<br>- Infer `Void` as the generic return type in method references and lambdas<br> without a `return`.<br><br>Non-Goals<br>---------<br><br>- `Void`-returning methods are still free to explicitly state `return null`.<br>- The scope should be limited such that:<br> - The compiler can do the heavy lifting, without any JVM changes.<br> - No new lambda objects would be constructed implicitly.</div><div><br>Motivation<br>----------<br><br>Lambdas provide powerful abstractions for working with closeable resources or in<br>temporary contexts. For example, we can define the following abstraction:<br><br> <R> R withBufferedReader(Path path, BufferedReaderFunction<R> f) {<br> try (var reader = Files.newBufferedReader(path)) {<br> return f.apply(reader);<br> } catch (IOException e) {<br> throw new UncheckedIOException(e);<br> }<br> }<br><br> @FunctionalInterface<br> interface BufferedReaderFunction<R> {<br> R apply(BufferedReader reader) throws IOException;<br> }<br><br>Callers can use this method as follows:<br><br> var firstLine = withBufferedReader(path, reader -> reader.lines().findFirst());<br><br>When a caller doesn't need the return value, though, the caller must include an<br>awkward `return null`:<br><br> withBufferedReader(path, reader -> {<br> this.header = reader.readLine();<br> this.data = reader.lines().toList();<br> return null;<br> });<br><br>These examples used `BufferedReader` for familiarity, but abstractions like this<br>are arguably more useful when reading from other persistent data stores (e.g.,<br>databases, search indexes) or when writing context-specific code. Using a<br>completely hypothetical `UserContextHolder`:<br><br> <R> R inUserContext(User user, Supplier<R> supplier) {<br> var oldContext = UserContextHolder.getContext();<br> UserContextHolder.setUserContext(user);<br> try {<br> return supplier.get();<br> } finally {<br> UserContextHolder.setContext(oldContext);<br> }<br> }<br><br>Then we might have system code that looks like the following:<br><br> interface Report {<br> User getUser();<br> GeneratedReport generate();<br> }<br><br> for (var report: pendingReports) {<br> save(inUserContext(report.getUser(), report::generate));<br> }<br><br> // or:<br><br> interface Job {<br> User getUser();<br> void execute();<br> }<br><br> for (var job : pendingJobs) {<br> inUserContext(job.getUser(), () -> {<br> job.execute();<br> return null;<br> });<br> // inUserContext(job.getUser(), job::execute) with this proposal<br> }<br><br>Description<br>-----------<br><br>When a method has a fully instantiated return type of `Void`, all of the<br>following would be valid:<br><br>- `return null`<br>- `return`<br>- omitted `return` (implicit `void` return)<br><br>`Void`-returning methods can continue using `return null` without warning. It<br>should be at least a warning, though, for a single method to mix `return null`<br>and any `void`-style return (implicit or explicit).<br><br>When a functional interface has a generic return type parameter, lambdas can use<br>`void`-style returns, and references to `void`-returning methods can be used,<br>where the compiler will infer the return type to be `Void` and logically insert</div><div>the necessary `return null` statements.<br><br>As with functional interfaces in general, an instance of one type cannot be<br>automatically converted to another. That is, a `Runnable` cannot be passed as a<br>`Supplier<T>`, inferring `T = Void`. `Runnable::run` could be used instead.<br><br>When a method returns a generic type, and when that type is inferred as `Void`<br>due to the use of an *enhanced `Void` return*, it would be an error to assign<br>the result to a variable. Returning to the `inUserContext` example:<br><br> GeneratedReport result = inUserContext(report.getUser(), report::generate); // OK<br><br> Void result = inUserContext(job.getUser(), () -> {<br> job.execute();<br> return null;<br> }); // OK: result is null (backwards compatible)<br><br> Void result = inUserContext(job.getUser(), job::execute); // ERROR<br><br> Void result = inUserContext(job.getUser(), () -> job.execute()); // ERROR<br><br>This enhancement would be implemented entirely by the compiler, generating JVM<br>bytecode as if `return null` had been used where it's currently needed.<br><br>Alternatives<br>------------<br><br>Returning to the `withBufferedReader` example, without this proposal, we can<br>instead define sibling methods (and types) to avoid the need for `return null`:<br><br> void withBufferedReader(Path path, BufferedReaderProcedure f) {<br> withBufferedReader(path, reader -> {<br> f.accept(reader);<br> return null;<br> });<br> }<br><br> interface BufferedReaderProcedure {<br> void accept(BufferedReader reader) throws IOException;<br> }<br><br> withBufferedReader(path, reader -> {<br> this.header = reader.readLine();<br> this.data = reader.lines().toList();<br> });<br><br>This works, sort of, but we quickly find that it doesn't actually work.<br>Returning to the first example:<br><br> var firstLine = withBufferedReader(path, reader -> reader.lines().findFirst());<br><br>This now fails to compile, reporting "Ambiguous method call." We can work around<br>that issue, of course, by standardizing on verb pairs to use in these scenarios:<br><br> R applyingBufferedReader(...)<br> void acceptingBufferedReader(...)<br><br>In reality, though, this disambiguation is not the biggest issue. Rather, in<br>most codebases, it would be hard to justify defining duplicate methods and<br>types. Many development teams would instead use `return null` statements where<br>necessary, avoid the abstraction in cases where `return null` is needed, or<br>avoid defining the abstraction altogether.<br><br>Risks and Assumptions<br>---------------------<br><br>Some codebases may have sibling methods like those described above, where this<br>enhancement would cause new "Ambiguous method call." when using the<br>`void`-returning method. However:<br><br>- Already-compiled code would not be affected since there is no JVM change.<br>- Impacted code would likely benefit from this enhancement, with new compiler<br> errors pointing to duplicate code that can likely be deleted.<br><br><br></div></div>
</blockquote></div>