Skip to content

odd formatting for lambdas #19

Closed

Description

@scottbessler

are the extra newlines intentional?

before:

    Stream<ItemKey> itemIdsStream = stream(members)
        .flatMap(m -> m.getFieldValues()
            .entrySet()
            .stream()
            .filter(fv -> itemLinkFieldIds.contains(fv.getKey()))
            .flatMap(fv -> FieldDTO.deserializeStringToListOfStrings(fv.getValue())
                .stream()
                .map(id -> new ItemKey(fieldsById.get(fv.getKey()).getItemTypeId(), id))));

or even

    Stream<ItemKey> itemIdsStream =
        stream(members)
            .flatMap(
                m -> m.getFieldValues()
                    .entrySet()
                    .stream()
                    .filter(fv -> itemLinkFieldIds.contains(fv.getKey()))
                    .flatMap(
                        fv -> FieldDTO.deserializeStringToListOfStrings(fv.getValue())
                            .stream()
                            .map(
                                id -> new ItemKey(
                                    fieldsById.get(fv.getKey()).getItemTypeId(), id))));

after:

    // items
    Stream<ItemKey> itemIdsStream =
        stream(members)
            .flatMap(
                m ->
                    m.getFieldValues().entrySet().stream()
                        .filter(fv -> itemLinkFieldIds.contains(fv.getKey()))
                        .flatMap(
                            fv ->
                                FieldDTO.deserializeStringToListOfStrings(fv.getValue()).stream()
                                    .map(
                                        id ->
                                            new ItemKey(
                                                fieldsById.get(fv.getKey()).getItemTypeId(), id))));

Activity

  1. kevinb9n commented on Dec 17, 2015

    @kevinb9n
    Contributor

    I don't think the problem is lambda-specific, but this lambda-using code is doing a really good job of tripping up on the general issue. If we can solve that issue, ideally this code ought to be able to show up as

    Stream<ItemKey> itemIdsStream =
        stream(members)
            .flatMap(m ->
                m.getFieldValues()
                    .entrySet()
                    .stream()
                    .filter(fv -> itemLinkFieldIds.contains(fv.getKey()))
                    .flatMap(fv ->
                        FieldDTO.deserializeStringToListOfStrings(fv.getValue())
                            .stream()
                            .map(id -> new ItemKey(fieldsById.get(fv.getKey()).getItemTypeId(), id))));
  2. cpovirk commented on Dec 17, 2015

    @cpovirk
    Member

    In addition to the general problem, do we also have something lambda-specific? Your example has the line break after ->, but the formatter apparently puts it before. Should that change?

    A result of the current behavior is that any chained calls or arguments to the next expression are indented 1 space from the expression, rather than 4. This is probably to spec, and IIRC it also comes up with the ternary operator, but it looks even weirder here because it's 1 space, rather than the 3 we see with the ternary operator. I'm talking about lines like this one...

                -> m.getFieldValues()
                    .entrySet()
    

    ...(which kind of works out because m happens to be one letter, perhaps a common case for lambda variables) and this one (which is hard to love)...

                                    -> new ItemKey(
                                        fieldsById.get(fv.getKey()).getItemTypeId(), id))));
    
  3. kevinb9n commented on Dec 17, 2015

    @kevinb9n
    Contributor

    Yes, the issue of whether to break before or after -> is on our list of style guide issues to resolve before we move to Java 8.

  4. cushon commented on Dec 17, 2015

    @cushon
    Collaborator

    The formatter started always breaking after -> in b8e6744. I thought that decision had been made already, but I think I misread @kevinb9n's comment in #2.

    Anyway, the current behaviour is:

    x ->
        y()
    
    x -> {
        y();
    }

    Instead of:

    x
        -> y()
    
    x
        -> {
            y();
        }

    I like the consistency between expression and statement lambdas, and it seems like the only way to support the suggested formatting of this example:

    Stream<ItemKey> itemIdsStream =
        stream(members)
            .flatMap(m ->
                m.getFieldValues()
                ...
  5. kevinb9n commented on Dec 17, 2015

    @kevinb9n
    Contributor

    The decision might not have been officially made, but it has been now. Keep -> { together, but otherwise break after ->.

  6. GuiSim commented on Mar 11, 2016

    @GuiSim

    Has the code been modified to reflect this decision?

  7. cushon commented on Mar 11, 2016

    @cushon
    Collaborator

    It has been modified to break after -> and -> {. It hasn't been modified to keep e.g. m -> on the previous line (we haven't decided not to do that, it just hasn't happened).

    There should be another release soon, or you can build it at head to see the current behaviour.

  8. jbduncan commented on Aug 17, 2016

    @jbduncan
    Contributor

    Hmm, I have some code which, before I tried applying google-java-format to it, I half-expected to end up looking like this:

    FooTraverser.of(fooGraph).preOrderTraversal(root).forEach(e -> 
        fooGraph.getSuccessors(e).forEach(child -> 
            treeCopy.addChild(graph.findEdge(e, child), e, child)));
    

    but when I actually did format it (using the version built from 00530c0), it looked like this:

    FooTraverser.of(fooGraph)
            .preOrderTraversal(root)
            .forEach(
                e ->
                    fooGraph
                        .getSuccessors(e)
                        .forEach(
                            child ->
                                treeCopy.addChild(graph.findEdge(e, child), e, child)));
    

    Is the current behaviour intentional?

  9. basil commented on Dec 23, 2016

    @basil

    It hasn't been modified to keep e.g. m -> on the previous line (we haven't decided not to do that, it just hasn't happened).

    Has a decision been made about this issue? It's really annoying that the lambda parameter and the arrow token always begin a new line. For example, consider how google-java-format indents this example:

    import java.util.Arrays;
    import java.util.stream.Stream;
    
    class LambdaExample {
      public static void main(String[] args) {
        String[] stringArr = {"a", "b", "c", "d"};
        Stream<String> stream = Arrays.stream(stringArr);
        stream.forEach(
            letter -> {
              System.out.print(letter);
            });
      }
    }
    

    This would look much nicer as follows:

    import java.util.Arrays;
    import java.util.stream.Stream;
    
    class LambdaExample {
      public static void main(String[] args) {
        String[] stringArr = {"a", "b", "c", "d"};
        Stream<String> stream = Arrays.stream(stringArr);
        stream.forEach(letter -> {
          System.out.print(letter);
        });
      }
    }
    

    This problem is exacerbated in AOSP mode. Here's how google-java-format indents the same example when in AOSP mode:

    import java.util.Arrays;
    import java.util.stream.Stream;
    
    class LambdaExample {
        public static void main(String[] args) {
            String[] stringArr = {"a", "b", "c", "d"};
            Stream<String> stream = Arrays.stream(stringArr);
            stream.forEach(
                    letter -> {
                        System.out.print(letter);
                    });
        }
    }
    

    Lambdas in AOSP mode are indented 12 spaces! This creates a huge amount of unnecessary whitespace and inhibits readability, especially compared to using a traditional for loop. I want to encourage people to use lambdas in our codebase, but it's difficult to do that when using them triples the indentation level and makes the code less readable. In contrast, it looks much nicer if the lambda parameter and arrow token are kept on the preceding line:

    import java.util.Arrays;
    import java.util.stream.Stream;
    
    class LambdaExample {
        public static void main(String[] args) {
            String[] stringArr = {"a", "b", "c", "d"};
            Stream<String> stream = Arrays.stream(stringArr);
            stream.forEach(letter -> {
                System.out.print(letter);
            });
        }
    }
    

    Notice how in the above, the body of the lambda is indented only four spaces from the forEach, the same amount as it would have been in a traditional for loop.

  10. jbduncan commented on Feb 10, 2017

    @jbduncan
    Contributor

    Hi @basil, I think you can workaround this nasty lambda indentation behaviour for a good number of cases in the meantime, by using method references[1] whenever possible.

    If I were to use the example you gave above as a template, then here's what I'd turn it into:

    import java.util.Arrays;
    import java.util.stream.Stream;
    
    class MethodReferenceExample {
        public static void main(String[] args) {
            String[] stringArr = {"a", "b", "c", "d"};
            Stream<String> stream = Arrays.stream(stringArr);
            stream.forEach(System.out::print);
        }
    }
    

    [1] Or refactoring the lambda expression into a private method, and calling it from a method reference or a new lambda...

  11. alexkleiman commented on Feb 10, 2017

    @alexkleiman

    @jbduncan that may work in some individual cases, but unfortunately, it is not feasible to apply this workaround when reformatting a codebase which already makes heavy use of lambdas. That being said, it is certainly a useful workaround to apply on a case-by-case basis until this bug is resolved.

  12. ilya40umov commented on Jul 10, 2017

    @ilya40umov

    +1 Formatting of lambdas in the current version (1.3) is quite annoying (gets unreadable quite easily).
    e.g.

        assertThat(response.getBody())
            .satisfies(
                body -> {
                  assertThat(body.data())
                      .hasValueSatisfying(
                          place -> {
                            assertThat(place.getId()).isEqualTo(253955);
                          });
                });
  13. 23 remaining items

  14. MrIvanPlays commented on Sep 7, 2019

    @MrIvanPlays

    Any progress on this?

  15. linusjf commented on Sep 7, 2019

    @linusjf

    What do you mean? No, I don't have access to clang-9 on termux, as yet.

    The only workaround for now is to manually format the code as expected and wrap it in // clang-format off and // clang-format on markers. That works if you're using clang-format. It will not prevent Google-Java-format from reformatting your code if it deems it necessary.

  16. linusjf commented on Sep 8, 2019

    @linusjf

    My usecase is to use Google-Java-format and clang-format in that order. If I don't like what clang-format does, I comment-block that section. Google-Java-format does a slightly better job, overall, but then I haven't access to clang-format 9.

  17. mpern commented on Jul 7, 2020

    @mpern

    Since almost a year passed after the last comment and there was a release of GJF recently (so at least the project isn't dead):

    Is there any progress on improving the formatting of lambdas by GJF?

  18. allensuner commented on Apr 20, 2021

    @allensuner

    I would like to echo the above comment I really don't feel like adding a line break or new line solely for the purpose of using a language feature is a good workaround.

  19. mpern commented on Apr 21, 2021

    @mpern

    If you want to have a solution now:

    Get rid of GJF and use spotless to run the Eclipse JDT formatter.

  20. kevinb9n commented on Aug 23, 2021

    @kevinb9n
    Contributor

    Wow. I apologize for not having given any update on this issue in so long (and while so much discussion happened). As I suppose is obvious, we haven't been paying attention in an "organized/committed" way but have just watched for low-hanging fruit.

    This one seems to be... high-hanging fruit:

    • The formatter is built on the principle of the "rectangle rule" and that's the source of most of what is good about it
    • The best behavior would be if it can recognize when exceptions to that rule are justified, and back off in those cases
    • This is sure as heck one of those cases
    • Yet it has proven surprisingly difficult to figure out how to make it do that without undue collateral damage
    • It seems that we (Google) don't necessarily have enough motivation to work on figuring this out. It's less nasty for us internally because of our 2-space indents and because we generally prefer to refactor nested lambdas into private methods (used via method reference).
  21. koppor commented on Oct 5, 2024

    @koppor

    Note that this behavior was the motivation of the fork "Palantir Java Format". However, they seem not to have any capacity to suport Java 21 (see palantir/palantir-java-format#952).

  22. added a commit that references this issue on Jan 17, 2026
  23. cushon commented on Oct 16, 2026

    @cushon
    Collaborator

    212d5fe changes lambda formatting to keep the start of the lambda (up to and including the ->) on the previous line if it fits.

    Using the example from #19 (comment), this changes the output from:

        Stream<ItemKey> itemIdsStream =
            stream(members)
                .flatMap(
                    m ->
                        m.getFieldValues().entrySet().stream()
                            .filter(fv -> itemLinkFieldIds.contains(fv.getKey()))
                            .flatMap(
                                fv ->
                                    FieldDTO.deserializeStringToListOfStrings(fv.getValue()).stream()
                                        .map(
                                            id ->
                                                new ItemKey(
                                                    fieldsById.get(fv.getKey()).getItemTypeId(), id))));

    to:

        Stream<ItemKey> itemIdsStream =
            stream(members)
                .flatMap(m ->
                    m.getFieldValues().entrySet().stream()
                        .filter(fv -> itemLinkFieldIds.contains(fv.getKey()))
                        .flatMap(fv ->
                            FieldDTO.deserializeStringToListOfStrings(fv.getValue()).stream()
                                .map(id ->
                                    new ItemKey(fieldsById.get(fv.getKey()).getItemTypeId(), id))));

    It took a while to figure out how to support this in a way that was consistent with the formatter's 'rectangle rule' approach. This was enabled by some other changes to the formatter which make it easier to express exceptions to the rectangle rule.

    If you have a chance to try out the changes in this issue before the next release, please file issues if you run into any bugs or edge cases, or if there were issues with lambda formatting in the examples in this bug that aren't addressed by the changes.

  24. Stephan202 commented on Oct 16, 2026

    @Stephan202
    Contributor

    Very nice! 🚀

    If you have a chance to try out the changes in this issue before the next release

    I ran a quick test with the Error Prone Support code base. I've gotten so used to the old format that my brain will need some adjusting, but it looks good!

    There was only one change that I would have expected to be different. In the diff below, the text inside the text block should also be deindented:

    @@ -289,12 +288,11 @@ public final class RefasterRuleCollection extends BugChecker implements Compilat
                 tree,
                 "The following matches unexpectedly occurred in method `%s`".formatted(tree.getName()),
                 unexpectedMatchesByLineNumber.entries().stream()
    -                .map(
    -                    e ->
    -                        """
    +                .map(e ->
    +                    """
                             Rule `%s` matches on line %s, while it should match in a method named \
                             `test%s`."""
    -                            .formatted(e.getValue(), e.getKey(), e.getValue()))
    +                        .formatted(e.getValue(), e.getKey(), e.getValue()))
                     .collect(toImmutableSet()),
                 state);
  25. cushon commented on Oct 19, 2026

    @cushon
    Collaborator

    Thanks @Stephan202! I have a fix for that text block issue in #1486

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions