PageSourceSearch

https://armeria.dev/assets/js/cc192260.a6733f27.js

js armeria.dev collected 2026-10-03 23:23:58 UTC 29,415 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkarmeria_site=self.webpackChunkarmeria_site||[]).push([["34"],{39072(e,n,i){i.r(n),i.d(n,{metadata:()=>s,default:()=>h,frontMatter:()=>l,contentTitle:()=>a,toc:()=>c,assets:()=>o});var s=JSON.parse('{"id":"developer-guide","title":"Developer guide","description":"Build requirements","source":"@site/src/content/community/developer-guide.mdx","sourceDirName":".","slug":"/developer-guide","permalink":"/community/developer-guide","draft":false,"unlisted":false,"editUrl":"https://github.com/line/armeria/edit/main/site/src/content/community/developer-guide.mdx","tags":[],"version":"current","frontMatter":{},"sidebar":"communitySidebar","previous":{"title":"Articles and slides","permalink":"/community/articles"},"next":{"title":"Code of conduct","permalink":"/community/code-of-conduct"}}'),r=i(74848),t=i(28453);let l={},a="Developer guide",o={},c=[{value:"Build requirements",id:"build-requirements",level:2},{value:"How to build",id:"how-to-build",level:2},{value:"Contributor license agreement",id:"contributor-license-agreement",level:2},{value:"Setting up your IDE",id:"setting-up-your-ide",level:2},{value:"Configure <code>-parameters</code> javac option",id:"configure--parameters-javac-option",level:2},{value:"Always make the build pass",id:"always-make-the-build-pass",level:2},{value:"Add copyright header",id:"add-copyright-header",level:2},{value:"Add Javadoc",id:"add-javadoc",level:2},{value:"Check the warnings from the inspection profile",id:"check-the-warnings-from-the-inspection-profile",level:2},{value:"Avoid redundancy",id:"avoid-redundancy",level:2},{value:"Use <code>public</code> only when necessary",id:"use-public-only-when-necessary",level:2},{value:"Organize",id:"organize",level:2},{value:"Check <code>null</code>",id:"check-null",level:2},{value:"Use <code>@Nullable</code>",id:"use-nullable",level:3},{value:"Avoid redundant null checks",id:"avoid-redundant-null-checks",level:3},{value:"Use meaningful exception messages",id:"use-meaningful-exception-messages",level:2},{value:"Validate",id:"validate",level:2},{value:"Use Guava&#39;s <code>Preconditions</code> if possible",id:"use-guavas-preconditions-if-possible",level:3},{value:"Prefer JDK API",id:"prefer-jdk-api",level:2},{value:"Prefer early-return style",id:"prefer-early-return-style",level:2},{value:"Prefer <code>MoreObjects.toStringHelper()</code>",id:"prefer-moreobjectstostringhelper",level:2},{value:"Think aesthetics",id:"think-aesthetics",level:2},{value:"Use JUnit 5 instead of JUnit 4 for testing",id:"use-junit-5-instead-of-junit-4-for-testing",level:2},{value:"Use AssertJ instead of JUnit&#39;s assertion API",id:"use-assertj-instead-of-junits-assertion-api",level:2},{value:"How to write pull request description",id:"how-to-write-pull-request-description",level:2},{value:"Motivation",id:"motivation",level:3},{value:"Modifications",id:"modifications",level:3},{value:"Result",id:"result",level:3},{value:"Integrating with Gradle Enterprise",id:"integrating-with-gradle-enterprise",level:2}];function d(e){let n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,t.R)(),...e.components},{Details:i,TabItem:s,Tabs:l}=n;return i||u("Details",!0),s||u("TabItem",!0),l||u("Tabs",!0),(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"developer-guide",children:"Developer guide"})}),"\n",(0,r.jsx)(n.h2,{id:"build-requirements",children:"Build requirements"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["OpenJDK 25 or its derivative, such as ",(0,r.jsx)(n.a,{href:"https://www.azul.com/downloads/?version=java-25&package=jdk#zulu",children:"Azul JDK"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Consider using a version manager for convenient installation of JDK, such as\n",(0,r.jsx)(n.a,{href:"https://asdf-vm.com/",children:"asdf"})," and ",(0,r.jsx)(n.a,{href:"https://github.com/shyiko/jabba",children:"jabba"}),"."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"how-to-build",children:"How to build"}),"\n",(0,r.jsxs)(n.p,{children:["We use ",(0,r.jsx)(n.a,{href:"https://gradle.org/",children:"Gradle"})," to build Armeria.\nThe following command will compile Armeria, run tests and generate JARs:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"$ ./gradlew --parallel build\n"})}),"\n",(0,r.jsx)(n.h2,{id:"contributor-license-agreement",children:"Contributor license agreement"}),"\n",(0,r.jsxs)(n.p,{children:["When you are sending a pull request and it's a non-trivial change beyond fixing typos, please sign\n",(0,r.jsx)(n.a,{href:"https://cla-assistant.io/line/armeria",children:"the ICLA (individual contributor license agreement)"}),". Please\n",(0,r.jsx)(n.a,{href:"mailto:[email protected]",children:"contact us"})," if you need the CCLA (corporate contributor license agreement)."]}),"\n",(0,r.jsx)(n.h2,{id:"setting-up-your-ide",children:"Setting up your IDE"}),"\n",(0,r.jsxs)(n.p,{children:["You can import Armeria into your IDE (",(0,r.jsx)(n.a,{href:"https://www.jetbrains.com/idea/",children:"IntelliJ IDEA"})," or ",(0,r.jsx)(n.a,{href:"https://www.eclipse.org/",children:"Eclipse"}),") as a Gradle project."]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["IntelliJ IDEA - 
1See ",(0,r.jsx)(n.a,{href:"https://www.jetbrains.com/help/idea/gradle.html#gradle_import_project_start",children:"Importing Project from Gradle Model"})]}),"\n",(0,r.jsxs)(n.li,{children:["Eclipse - Use ",(0,r.jsx)(n.a,{href:"https://marketplace.eclipse.org/content/buildship-gradle-integration",children:"Buildship Gradle Integration"})]}),"\n"]}),"\n",(0,r.jsx)(n.admonition,{type:"tip",children:(0,r.jsx)(n.p,{children:"IntelliJ IDEA is our primary IDE for developing Armeria."})}),"\n",(0,r.jsxs)(n.p,{children:["Before importing the project, run the ",(0,r.jsx)(n.code,{children:"generateSources"})," task to generate some source files:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"$ ./gradlew --parallel -PnoLint generateSources\n"})}),"\n",(0,r.jsx)(n.admonition,{type:"tip",children:(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"-PnoLint"})," disables running lint tools such as Checkstyle and ESLint to save build time."]})}),"\n",(0,r.jsx)(n.p,{children:"After importing the project, import the IDE settings as well:"}),"\n",(0,r.jsxs)(l,{groupId:"ide-settings",children:[(0,r.jsx)(s,{label:"IntelliJ IDEA",value:"idea",children:(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"https://github.com/line/armeria/raw/main/settings/intellij_idea/settings.jar",children:(0,r.jsx)(n.code,{children:"settings.jar"})})," -\nSee ",(0,r.jsx)(n.a,{href:"https://www.jetbrains.com/help/idea/sharing-your-ide-settings.html#7a4f08b8",children:"Import settings from a ZIP archive"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Make sure to use 'LY OSS' code style and inspection profile.","\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Go to ",(0,r.jsx)(n.code,{children:"Preferences"})," > ",(0,r.jsx)(n.code,{children:"Editors"})," > ",(0,r.jsx)(n.code,{children:"Code Style"})," and set ",(0,r.jsx)(n.code,{children:"Scheme"})," option to ",(0,r.jsx)(n.code,{children:"LY OSS"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Go to ",(0,r.jsx)(n.code,{children:"Preferences"})," > ",(0,r.jsx)(n.code,{children:"Editors"})," > ",(0,r.jsx)(n.code,{children:"Inspections"})," and set ",(0,r.jsx)(n.code,{children:"Profile"})," option to ",(0,r.jsx)(n.code,{children:"LY OSS"}),"."]}),"\n"]}),"\n"]}),"\n"]})}),(0,r.jsx)(s,{label:"Eclipse",value:"eclipse",children:(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"https://github.com/line/armeria/raw/main/settings/eclipse/formatter.xml",children:(0,r.jsx)(n.code,{children:"formatter.xml"})})," -\nSee ",(0,r.jsx)(n.a,{href:"https://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Freference%2Fpreferences%2Fjava%2Fcodestyle%2Fref-preferences-formatter.htm",children:"Code Formatter Preferences"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"https://github.com/line/armeria/raw/main/settings/eclipse/formatter.importorder",children:(0,r.jsx)(n.code,{children:"formatter.importorder"})})," -\nSee ",(0,r.jsx)(n.a,{href:"https://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Freference%2Fpreferences%2Fjava%2Fcodestyle%2Fref-preferences-organize-imports.htm",children:"Organize Imports Preferences"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.a,{href:"https://github.com/line/armeria/raw/main/settings/eclipse/cleanup.xml",children:(0,r.jsx)(n.code,{children:"cleanup.xml"})})," -\nSee ",(0,r.jsx)(n.a,{href:"https://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Freference%2Fpreferences%2Fjava%2Fcodestyle%2Fref-preferences-cleanup.htm",children:"Clean Up Preferences"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:["Configure ",(0,r.jsx)(n.a,{href:"https://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Freference%2Fpreferences%2Fjava%2Feditor%2Fref-preferences-save-actions.htm",children:"Java Save Actions Preferences"}),".","\n",(0,r.jsxs)(i,{children:[(0,r.jsx)("summary",{children:"Click here to see the screenshot."}),(0,r.jsx)("img",{src:"https://github.com/line/armeria/raw/main/settings/eclipse/save_actions.png"})]}),"\n"]}),"\n"]})})]}),"\n",(0,r.jsxs)(n.h2,{id:"configure--parameters-javac-option",children:["Configure ",(0,r.jsx)(n.code,{children:"-parameters"})," javac option"]}),"\n",(0,r.jsxs)(n.p,{children:["You can configure your build tool and IDE to add ",(0,r.jsx)(n.code,{children:"-parameters"})," javac option.\nPlease refer to ",(0,r.jsxs)(n.a,{href:"/docs/setup#configure--parameters-javac-option",children:["Configure ",(0,r.jsx)(n.code,{children:"-parameters"})," javac option"]})," for more information."]}),"\n",(0,r.jsx)(n.h2,{id:"always-make-the-build-pass",children:"Always make the build pass"}),"\n",(0,r.jsx)(n.p,{children:"Make sure your change does not break the build."}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Run ",(0,r.jsx)(n.code,{children:"./gradlew --parallel build"})," locally."]}),"\n",(0,r.jsx)(n.li,{children:"It is likely that you'll encounter some Checkstyle or Javadoc errors.\nPlease fix them because otherwise the build will be broken."}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"add-copyright-header",children:"Add copyright header"}),"\n",(0,r.jsx)(n.p,{children:"All source files must begin with the following copyright header:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:'Copyright $today.year LY Corporation\n\nLY Corporation licenses this file to you under the Apache License,\nversion 2.0 (the "License");
1 you may not use this file except in compliance\nwith the License. You may obtain a copy of the License at:\n\n  https://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an "AS IS" BASIS, WITHOUT\nWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\nLicense for the specific language governing permissions and limitations\nunder the License.\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Please avoid introducing noise into your PRs by changing the date (",(0,r.jsx)(n.code,{children:"$today.year"}),") for every file you modify.\nIn IntelliJ, make sure you\n",(0,r.jsx)(n.a,{href:"https://www.jetbrains.com/help/idea/copyright.html#update-copyright-on-save",children:"disable"})," automatic copyright header updates."]}),"\n",(0,r.jsx)(n.h2,{id:"add-javadoc",children:"Add Javadoc"}),"\n",(0,r.jsxs)(n.p,{children:["All public classes and public or protected methods must have Javadoc,\nexcept the classes under ",(0,r.jsx)(n.code,{children:"com.linecorp.armeria.internal"}),". Referring to\nan internal API from a public API will trigger build failures."]}),"\n",(0,r.jsx)(n.h2,{id:"check-the-warnings-from-the-inspection-profile",children:"Check the warnings from the inspection profile"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Make sure you are using 'LY OSS' code style and inspection profile."}),"\n",(0,r.jsxs)(n.li,{children:["Evaluate all warnings emitted by the 'LY OSS' inspection profile.","\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["Try to fix them all and use the ",(0,r.jsx)(n.code,{children:"@SuppressWarnings"})," annotation if it's a false positive."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"avoid-redundancy",children:"Avoid redundancy"}),"\n",(0,r.jsx)(n.p,{children:"Avoid using redundant keywords. To list a few:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"final"})," method modifier in a ",(0,r.jsx)(n.code,{children:"final"})," class"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"static"})," or ",(0,r.jsx)(n.code,{children:"public"})," modifier in an ",(0,r.jsx)(n.code,{children:"interface"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"public"})," method modifier in a package-local or private class"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"private"})," constructor modifier in an ",(0,r.jsx)(n.code,{children:"enum"})]}),"\n",(0,r.jsxs)(n.li,{children:["field access prefixed with ",(0,r.jsx)(n.code,{children:"this."})," where unnecessary"]}),"\n"]}),"\n",(0,r.jsxs)(n.h2,{id:"use-public-only-when-necessary",children:["Use ",(0,r.jsx)(n.code,{children:"public"})," only when necessary"]}),"\n",(0,r.jsxs)(n.p,{children:["The classes, methods and fields that are not meant to be used by a user should not be\npublic. Use the most restrictive modifier wherever possible, such as ",(0,r.jsx)(n.code,{children:"private"}),",\npackage-local and ",(0,r.jsx)(n.code,{children:"protected"}),", so that static analysis tools can find dead code easily."]}),"\n",(0,r.jsx)(n.h2,{id:"organize",children:"Organize"}),"\n",(0,r.jsxs)(n.p,{children:["Organize class members carefully for readability, using ",(0,r.jsx)(n.strong,{children:"top-down"})," approach.\nAlthough there's no absolute rule of thumb, it's usually like:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"static"})," fields"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"static"})," methods"]}),"\n",(0,r.jsx)(n.li,{children:"member fields"}),"\n",(0,r.jsx)(n.li,{children:"constructors"}),"\n",(0,r.jsx)(n.li,{children:"member methods"}),"\n",(0,r.jsxs)(n.li,{children:["utility methods (both ",(0,r.jsx)(n.code,{children:"static"})," and member)"]}),"\n",(0,r.jsx)(n.li,{children:"inner classes"}),"\n"]}),"\n",(0,r.jsxs)(n.h2,{id:"check-null",children:["Check ",(0,r.jsx)(n.code,{children:"null"})]}),"\n",(0,r.jsxs)(n.p,{children:["Do explicit ",(0,r.jsx)(n.code,{children:"null"}),"-check on the parameters of user-facing public methods.\nAlways use ",(0,r.jsx)(n.code,{children:"Objects.requireNonNull(Object, String)"})," to do a ",(0,r.jsx)(n.code,{children:"null"}),"-check."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'import static java.util.Objects.requireNonNull;\n\npublic void setProperty(String name, String value) {\n    // Great\n    this.name = requireNonNull(name, "name");\n    // Not great - we may not know which parameter is null exactly.\n    this.name = requireNonNull(name);\n    // Not great - too verbose. NPE implies something\'s null already.\n    this.name = requireNonNull(name, "name is null");\n    // Not OK\n    this.name = name\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["If you are using IntelliJ IDEA and you imported the ",(0,r.jsx)(n.code,{children:"settings.jar"})," as explained\nabove, try the live template ",(0,r.jsx)(n.code,{children:"rnn"})," and ",(0,r.jsx)(n.code,{children:"rnna"})," which will save a lot of time."]}),"\n",(0,r.jsxs)(n.h3,{id:"use-nullable",children:["Use ",(0,r.jsx)(n.code,{children:"@Nullable"})]}),"\n",(0,r.jsxs)(n.p,{children:["Use ",(0,r.jsx)(n.code,{children:"@Nullable"})," annotation for nullable parameters and return types.\nDo not use ",(0,r.jsx)(n.code,{children:"@Nonnull"})," annotation since we assume everything is non-null otherwise."]}),"\n",(0,r.jsx)(n.h3,{id:"avoid-redundant-null-checks",children:"Avoid redundant null checks"}),"\n",(0,r.jsxs)(n.p,{children:["Avoid unnecessary ",(0,r.jsx)(n.code,{children:"null"}),"-checks, including the hidden checks in ",(0,r.jsx)(n.code,{children:"Objects.hashCode()"})," and ",(0,r.jsx)(n.code,{children:"Objects.equals()"}),"."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:"public final class MyClass {\n    private final Str
1ing name;\n\n    public MyClass(String name) {\n        // We are sure 'name' is always non-null.\n        this.name = requireNonNull(name, \"name\");\n    }\n\n    @Override\n    public int hashCode() {\n        // OK\n        return name.hashCode();\n        // Not OK\n        return Objects.hash(name);\n    }\n\n    @Override\n    public boolean equals(@Nullable Object obj) {\n        ... usual type check ...\n        // OK\n        return name.equals(((MyClass) obj).name);\n        // Not OK\n        return Objects.equals(name, ((MyClass) obj).name);\n    }\n}\n"})}),"\n",(0,r.jsx)(n.h2,{id:"use-meaningful-exception-messages",children:"Use meaningful exception messages"}),"\n",(0,r.jsx)(n.p,{children:"When raising an exception, specify meaningful message which gives an explicit clue\nabout what went wrong."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'switch (fileType) {\n    case TXT: ... break;\n    case XML: ... break;\n    default:\n        // Note that the exception message contains the offending value\n        // as well as the expected values.\n        throw new IllegalStateException(\n                "unsupported file type: " + fileType +\n                 " (expected: " + FileType.TXT + " or " + FileType.XML + \')\');\n}\n'})}),"\n",(0,r.jsx)(n.h2,{id:"validate",children:"Validate"}),"\n",(0,r.jsx)(n.p,{children:"Do explicit validation on the parameters of user-facing public methods.\nWhen raising an exception, always specify the detailed message in the following format:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'public void setValue(int value) {\n    if (value < 0) {\n        // Note that the exception message contains the offending value\n        // as well as the expected value.\n        throw new IllegalArgumentException("value: " + value + " (expected: >= 0)");\n    }\n}\n'})}),"\n",(0,r.jsxs)(n.h3,{id:"use-guavas-preconditions-if-possible",children:["Use Guava's ",(0,r.jsx)(n.code,{children:"Preconditions"})," if possible"]}),"\n",(0,r.jsxs)(n.p,{children:["Guava's ",(0,r.jsx)(n.code,{children:"Preconditions"})," provides ",(0,r.jsx)(n.code,{children:"checkArgument()"})," and ",(0,r.jsx)(n.code,{children:"checkState()"})," which can simplify argument or state validation logic:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'private boolean someState;\nvoid doSomething(int value) {\n    checkArgument(value > 0, "value: %s (expected: > 0)", value);\n    checkState(someState, "Cannot be called when ...");\n    ...\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["However, you should use a good old ",(0,r.jsx)(n.code,{children:"if"})," block if it takes an extra job to create an exception message:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'if (value <= 0) {\n    throw new IllegalArgumentException(\n        "value: " + toHumanReadable(value) + " (expected: ...)");\n}\n'})}),"\n",(0,r.jsx)(n.h2,{id:"prefer-jdk-api",children:"Prefer JDK API"}),"\n",(0,r.jsx)(n.p,{children:"Prefer using plain JDK API when the same behavior can be achieved with the same\namount of code."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:"// Prefer A (JDK) - less indirection\nMap<String, String> map = new HashMap<>();   // A (JDK)\nMap<String, String> map = Maps.newHashMap(); // B (Guava)\n\n// Prefer B (Guava) - simpler yet more efficient\nList<String> list = Collections.unmodifiableList(  // A (JDK)\n        otherList.stream().filter(...).collect(Collectors.toList()));\nList<String> list = otherList.stream().filter(...) // B (Guava)\n        .collect(toImmutableList());\n"})}),"\n",(0,r.jsx)(n.h2,{id:"prefer-early-return-style",children:"Prefer early-return style"}),"\n",(0,r.jsx)(n.p,{children:"Prefer 'early return' code style for readability."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:"// Great\npublic void doSomething(String value) {\n    if (value == null) {\n        return;\n    }\n\n    // Do the actual job\n}\n\n// Not great\npublic void doSomething(String value) {\n    if (value != null) {\n        // Do the actual job\n    }\n}\n"})}),"\n",(0,r.jsx)(n.p,{children:"However, when the 'normal' execution path is very simple, this may also look beautiful:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:"@Nullable\npublic String doSomething(String value) {\n    if (value != null) {\n        return value.trim();\n    } else {\n        return null;\n    }\n}\n"})}),"\n",(0,r.jsxs)(n.h2,{id:"prefer-moreobjectstostringhelper",children:["Prefer ",(0,r.jsx)(n.code,{children:"MoreObjects.toStringHelper()"})]}),"\n",(0,r.jsxs)(n.p,{children:["Prefer ",(0,r.jsx)(n.code,{children:"MoreObjects.toStringHelper()"})," to hand-written ",(0,r.jsx)(n.code,{children:"toString()"})," implementation.\nHowever, consider writing hand-written or caching ",(0,r.jsx)(n.code,{children:"toString()"})," implementation\nin performance-sensitive places."]}),"\n",(0,r.jsx)(n.h2,{id:"think-aesthetics",children:"Think aesthetics"}),"\n",(0,r.jsx)(n.p,{children:"Do not insert an empty line that hurts code aesthetics."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:"// OK\nif (...) {\n    doSomething();\n}\n\n// Not OK\nif (...) {\n    doSomething();\n                        // <-- Remove this extra line.\n}\n"})}),"\n",(0,r.jsx)(n.p,{children:"Similarly, do not use two or more consecutive empty lines."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:"// OK\npublic void a() { ... }\n\npublic void b() { ... }\n\n// Not OK\npublic void a() { ... }\n\n                        // <-- Remove this extra line.\npublic void b() { ... }\n"})}),"\n",(0,r.jsx)(n.h2,{id:"use-junit-5-instead-of-junit-4-for-testing",children:"Use JUnit 5 instead of JUnit 4 for testing"}),"\n",(0,r.jsxs)(n.p,{children:["We support both ",(0,r.jsx)(n.a,{href:"https://junit.org/junit4/",children:"JUnit 4"})," and ",(0,r.jsx)(n.a,{href:"https://junit.org/junit5/",children:"JUnit 5"})," for testing, but we recommend to use JUnit 5."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:"// Imports of JUnit5, Good\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n...\n\n// Imports of JUnit4, Not Good\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\n...\n"})}),"\n",(0,r.jsx)(n.h2,{id:"use-assertj-instead-of-junits-assertion-api",children:"Use AssertJ instead of JUnit's assertion API"}),"\n",(0,r.jsxs)(n.p,{children:["We prefer ",(0,r.jsx)(n.a,{href:"https://joel-costigliola.github.io/assertj/",children:"AssertJ"})," when writing assertions for test cases."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'// Good\nassertThat(actualValue).isEqualTo(expectedValue);\nassertThatThrownBy(() -> badMethod()).isInstanceOf(IllegalArgumentException.class)\n                                     .hasMessageContaining("bad method");\n// Not Good\nassertEquals(expectedValue, actualValue);\ntry {\n    badMethod();\n} catch (IllegalArgumentException e) {\n    assertTrue(e.getMessage().contains("bad method"));\n}\n'})}),"\n",(0,r.jsx)(n.h2,{id:"how-to-write-pull-request-description",children:"How to write pull request description"}),"\n",(0,r.jsx)(n.p,{children:"Writing a good pull request description is important to both contributors and reviewers because:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"it enables a contributor to communicate the intention and context of a pull request more clearly with reviewers."}),"\n",(0,r.jsxs)(n.li,{children:["it helps the developers write good ",(0,r.jsx)(n.a,{href:"/release-notes/",children:"release notes"}),"."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["How much detail should a pull request description have? The general rule of thumb is\nto put all ",(0,r.jsx)(n.em,{children:"notable"})," changes in detail. It doesn't have to contain every single tiny\ndetail of the changes.\nUsually, you need to fill the following 3 sections: Motivation, Modifications and Result."]}),"\n",(0,r.jsx)(n.h3,{id:"motivation",children:"Motivation"}),"\n",(0,r.jsx)(n.p,{children:"Explain why you're sending the pull request and what problem you're trying to solve.\nYou do not have to include all the detail but please include as much as the reviewers can get the background\nof this change.\nIf there are related GitHub issues, please leave links to them.\nIf you referred other resources (e.g. RFCs) for making this change, please leave links to them as well."}),"\n",(0,r.jsx)(n.p,{children:"These are good examples:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://github.com/line/armeria/pull/3479#issue-621862219",children:"#3479 (comment)"})}),"\n",(0,r.jsx)(n.li,{children:(0,r.jsx)(n.a,{href:"https://github.com/line/armeria/pull/3331#issue-567485208",children:"#3331 (comment)"})}),"\n"]}),"\n",(0,r.jsx)(n.h3,{id:"modifications",children:"Modifications"}),"\n",(0,r.jsxs)(n.p,{children:["List the modifications you've made in detail.\nAgain, you do not have to include all the modifications you made but ",(0,r.jsx)(n.em,{children:"notable"})," changes that you wish the\nreaders know.\nIf the pull request has a deprecation or breaking change, you need to describe it here. For example:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"- (
1Deprecated) Foo.bar() is deprecated.\n  - Use Baz.bar() instead.\n"})}),"\n",(0,r.jsx)(n.h3,{id:"result",children:"Result"}),"\n",(0,r.jsxs)(n.p,{children:["Specify ",(0,r.jsx)(n.code,{children:"- Closes #<GitHub issue number>"})," if this resolves the issue.\nDescribe the consequences that a user will face after this pull request is merged."]}),"\n",(0,r.jsxs)(n.p,{children:["For example, if the pull request fixes a bug, you can write: ",(0,r.jsx)(n.code,{children:"You no longer see a Foo exception when using Bar."}),"\nIf you introduce a new feature, you can write: ",(0,r.jsx)(n.code,{children:"You can now do A using B."}),"\nThese are examples:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["You no longer see a ",(0,r.jsx)(n.code,{children:"NullPointerException"})," when a request times out."]}),"\n",(0,r.jsxs)(n.li,{children:["You can now monitor the state of all live threads and heap using ",(0,r.jsx)(n.code,{children:"ManagementService"}),"."]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Add an example snippet if this pull request introduces a new feature, so we can use it in\nour ",(0,r.jsx)(n.a,{href:"/release-notes",children:"release notes"}),", e.g."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'Server.builder()\n      .serviceUnder("/internal/management/", ManagementService.of());\n'})}),"\n",(0,r.jsx)(n.h2,{id:"integrating-with-gradle-enterprise",children:"Integrating with Gradle Enterprise"}),"\n",(0,r.jsxs)(n.p,{children:["You may want to integrate your local development environment with ",(0,r.jsx)(n.a,{href:"https://gradle.com/",children:"Gradle Enterprise"}),".\nBy doing so, you may:"]}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsx)(n.li,{children:"Visualize and troubleshoot build issues more easily."}),"\n",(0,r.jsx)(n.li,{children:"Experience faster builds due to build caches."}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Let us know at the ",(0,r.jsx)(n.a,{href:"/s/discord",children:"Armeria Discord"})," channel, and we'll create an account\nfor you. Afterwards, you may integrate your local environment with the following command:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"./gradlew provisionGradleEnterpriseAccessKey\n"})})]})}function h(e={}){let{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}function u(e,n){throw Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}},28453(e,n,i){i.d(n,{R:()=>l,x:()=>a});var s=i(96540);let r={},t=s.createContext(r);function l(e){let n=s.useContext(t);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:l(e.components),s.createElement(t.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.