skip to content

Project Structure & Layout

How a multi-project build is laid out: include, hierarchical project paths, custom directories, flat layouts, and the settings-versus-build-script split. Interviewers ask because structure chosen badly early is expensive to change later.

on this pageshow

questions

30

What is a Gradle project path, and how does it differ from a project name?

level: juniorimportance: must knowfreq 55%

answer

  1. path = colon-separated, rooted at ':'
  2. name = last segment only
  3. name not globally unique, path is
  4. ':services:auth' vs name 'auth'
  5. logical path != physical dir

basics

~10 s

A project path is a colon-separated identifier like ':services:auth' that uniquely locates a project in the build tree. The name is just the last segment ('auth'); the path encodes its full position.

solid answer

~40 s

Every project in a multi-project Gradle build has a **path** and a **name**. The path is a colon-separated string rooted at the build root — `:` is the root project, `:services` is a child, `:services:auth` is a grandchild. It uniquely identifies the project across the whole build. The **name** is only the final path segment (`auth`) and need not be unique on its own — two projects under different parents can both be named `auth` because their paths differ. You use the path everywhere you reference another project: `include(':services:auth')` in `settings.gradle.kts`, `project(':services:auth')` in dependencies, and `:services:auth:build` to invoke a task. The path is logical, not necessarily filesystem-based — by default it mirrors the directory layout, but you can override the physical directory with `projectDir`.

code

kotlin · 7 lines
kotlin
// settings.gradle.kts
rootProject.name = "platform"
include(":services:auth")

// In any build.gradle.kts you can inspect them:
// project.path  -> ":services:auth"
// project.name  -> "auth"

go deeper

for a junior

Know that ':services:auth' is a path, 'auth' is the name (last segment), and the path uniquely identifies a project.

for a middle

Explain that names can repeat but paths cannot, and that paths are used for dependencies and task addressing.

for a senior

Discuss the logical-vs-physical decoupling (projectDir remapping) and how paths drive task/dependency resolution.

for a principal

Frame path conventions as part of a module naming/governance strategy in large monorepos, and the trade-offs of deep vs flat hierarchies.

## Path vs Name In a Gradle multi-project build, projects form a tree. Each node has two identifiers: - **Project path** — a colon-separated string describing the node's position in the tree, starting from the root. The root project's path is the single colon `:`. A direct child is `:services`; a child of that is `:services:auth`. The path is **globally unique** within the build. - **Project name** — the *last segment* of the path. For `:services:auth` the name is `auth`. Names are **not** required to be globally unique; `:services:auth` and `:platform:auth` can coexist because their full paths differ. ## Why two identifiers? Gradle needs an unambiguous handle to wire dependencies, resolve tasks, and address configuration. The path provides that. The name is a human-friendly short label and is what gets used (by default) for the published artifact's `archivesBaseName` and the project directory name. ## Where paths show up ```kotlin // settings.gradle.kts — declares the project into the tree include(":services:auth") // build.gradle.kts — a project dependency by path dependencies { implementation(project(":services:auth")) } ``` And on the command line, a task is addressed by the project path plus task name: `./gradlew :services:auth:test`. ## Path is logical, directory is physical By default the path `:services:auth` maps to the directory `services/auth` relative to the root. But the two are decoupled: in `settings.gradle.kts` you can remap `project(":services:auth").projectDir = file("modules/authentication")`. The path stays `:services:auth` regardless of where the files live. ## Accessing the root The leading colon means "from the root". `:auth` is a top-level project named `auth`; `auth` (no leading colon) in a task invocation is interpreted relative to the current project, which is rarely what you want in scripts — always qualify references with a leading colon for clarity.

  • Can two projects in the same build share a name?
    Yes, as long as their full paths differ — e.g. ':services:auth' and ':platform:auth' both have name 'auth' but distinct, unique paths.
  • What is the path of the root project?
    A single colon ':'. Its name is whatever rootProject.name is set to in settings.gradle.kts.

The path is like a file system absolute path (/services/auth) — unique and positional; the name is just the file name (auth), which can repeat in different folders.

saying these in an interview costs you the question

  • Saying the project name must be unique across the whole build (only the path must be).
  • Claiming the path always equals the directory path — it's logical and can be remapped with projectDir.

context

open as a page

How do you register subprojects in a Gradle multi-project build, and which file does that belong in?

level: juniorimportance: must knowfreq 75%

basics

~10 s

In settings.gradle.kts you call include(":app", ":lib"). Each path becomes a project in the build. Subprojects are NOT declared in build.gradle.kts — only the settings file defines which projects exist.

open as a page

What does customizing a subproject's projectDir in settings.gradle.kts let you do, and how do you set it?

level: juniorimportance: must knowfreq 45%

basics

~10 s

It decouples a project's logical path (like ':lib') from its folder on disk. In settings.gradle.kts you write project(":lib").projectDir = file("libs/lib") so ':lib' lives in a different directory than the default ./lib.

open as a page

In a Gradle multi-project build, what is the difference in responsibility between settings.gradle.kts and build.gradle.kts?

level: juniorimportance: must knowfreq 80%

basics

~10 s

settings.gradle.kts defines the build's structure — which projects exist (via include). build.gradle.kts defines what a single project does — its plugins, dependencies, and tasks.

open as a page

In a Gradle multi-project build, what determines the order in which projects are configured (evaluated), and is that order guaranteed?

level: middleimportance: must knowfreq 55%

basics

~10 s

Gradle configures projects in an order it chooses; by default the root is evaluated first, but the order among other projects is not something you should rely on unless you force it.

open as a page

How do you reference and configure a deeply nested subproject from another project, using its hierarchical path?

level: middleimportance: must knowfreq 50%

basics

~10 s

Use the full colon-separated path with project(':services:auth'). It returns the Project for that nested module, which you can use in dependencies(project(':services:auth')) or to configure it, e.g. project(':services:auth') { ... }.

open as a page

Explain precisely how includeFlat resolves a project's directory, and how that resolution differs from include. What is the equivalent you could write by hand?

level: middleimportance: must knowfreq 30%

basics

~10 s

include resolves the project dir under rootDir (rootDir/name); includeFlat resolves it under rootDir's parent (rootDir/../name). You can reproduce includeFlat by calling include then setting project(':name').projectDir to file('../name').

open as a page

Explain the difference between a project's logical path (the include argument) and its directory on disk.

level: middleimportance: must knowfreq 55%

basics

~10 s

The include string (e.g. ':services:auth') is a logical project path used to identify the project. By default Gradle maps it to a matching folder (services/auth), but the path and the directory are separate concepts.

open as a page

Why do pluginManagement and dependencyResolutionManagement live in settings.gradle.kts rather than in a build script?

level: middleimportance: must knowfreq 60%

basics

~10 s

Gradle must know how to resolve plugins and dependencies before configuring any project. Settings is evaluated first, build-wide, so resolution rules and version catalogs belong there.

open as a page

Why is eagerly reading another project's state at configuration time fragile, and how do lazy Providers fix it?

level: seniorimportance: must knowfreq 50%

basics

~10 s

Reading another project at configuration time can run before that project is configured, giving stale values. Lazy Providers defer the read until execution, when everything is configured, so order no longer matters.

open as a page

After includeFlat('shared'), how do you reference that project as a dependency from another module, and does its sibling directory location change the dependency syntax?

level: juniorimportance: should knowfreq 18%

basics

~10 s

You reference it by its project path, project(':shared'), exactly like any included project. The sibling directory on disk does not change the dependency syntax — only its projectDir differs.

open as a page

How do allprojects { } and subprojects { } interact with configuration ordering, and why can configuration injection be deferred safely?

level: middleimportance: should knowfreq 30%

basics

~10 s

allprojects/subprojects in the root register configuration that Gradle applies to each project when that project is evaluated. It's deferred per project, so it doesn't depend on a fixed evaluation order.

open as a page

How does include(':services:auth') build out the project tree, and what does it imply about intermediate projects?

level: middleimportance: should knowfreq 40%

basics

~10 s

include(':services:auth') registers the leaf and implicitly creates any missing intermediate projects on the path (here ':services'). Each segment becomes a project node; intermediate ones exist even without their own build file.

open as a page

What does the leading colon mean in a Gradle project or task path, and why should you usually include it?

level: middleimportance: should knowfreq 45%

basics

~10 s

A leading colon means the path is absolute — resolved from the root project. Without it, the path is relative to the current project. Leading-colon (absolute) references are unambiguous, so scripts should use them.

open as a page

What does includeFlat('sibling-project') do in a Gradle settings file, and how does the resulting project layout differ from a normal include?

level: middleimportance: should knowfreq 35%

basics

~10 s

includeFlat adds a project that lives in a sibling directory next to the root project (../sibling), instead of beneath the root like include does.

open as a page

What is the rootProject reference in settings.gradle.kts, and what would you commonly use it for?

level: middleimportance: should knowfreq 35%

basics

~20 s

rootProject is the ProjectDescriptor for the build's single root project, available in the settings file. The most common use is rootProject.name = "..." to give the build a stable name instead of defaulting to the root folder name.

open as a page

How do you make a subproject use a build file with a non-default name, and why might you want to?

level: middleimportance: should knowfreq 30%

basics

~10 s

Set the descriptor's buildFileName in settings.gradle.kts: project(":lib").buildFileName = "lib.gradle.kts". Gradle then reads that file instead of the default build.gradle.kts in the project directory.

open as a page

You want short logical paths like ':auth' and ':billing' but the folders grouped under services/ on disk. How do you wire that in settings?

level: middleimportance: should knowfreq 35%

basics

~10 s

Include each project with a flat path and override projectDir to the grouped folder: include(":auth"); project(":auth").projectDir = file("services/auth"). The path stays flat while the directory is nested under services/.

open as a page

How does Gradle locate and load settings.gradle.kts and each build.gradle.kts, and how does that affect the root project name?

level: middleimportance: should knowfreq 35%

basics

~10 s

Gradle searches upward from the current directory for settings.gradle(.kts) to find the build root. Each project loads build.gradle.kts from its projectDir. The root project name defaults to the root directory name.

open as a page

What are the Settings and Project objects in Gradle, and how do the settings and build scripts relate to them?

level: middleimportance: should knowfreq 55%

basics

~10 s

Each settings.gradle.kts is the body of a Settings object; each build.gradle.kts is the body of a Project object. Methods you call (include, dependencies) are members of those objects.

open as a page

A property read from another subproject is sometimes correct and sometimes the default value across builds. How do you diagnose and fix it?

level: seniorimportance: should knowfreq 28%

basics

~10 s

It's almost certainly an evaluation-order bug: you're reading the other project eagerly at configuration time and it isn't always configured first. Replace the eager read with a lazy Provider, or force order with evaluationDependsOn.

open as a page

When would you reach for evaluationDependsOn(':lib') versus evaluationDependsOnChildren(), and what are the risks?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Use evaluationDependsOn(':lib') in one project to force a specific other project to be configured first; use evaluationDependsOnChildren() in a parent to configure all its children first. Both can mask design issues and slow configuration.

open as a page

When does a project's hierarchical path stop matching its directory layout, and how do you handle it deliberately?

level: seniorimportance: should knowfreq 32%

basics

~20 s

The path is logical; the directory is physical. By default ':services:auth' maps to services/auth, but you can remap projectDir in settings.gradle.kts so the same path lives anywhere on disk, decoupling logical structure from physical layout.

open as a page

A teammate added includeFlat('common') and now the build fails on a fresh CI checkout with 'project directory does not exist'. What is going on and how do you fix or harden it?

level: seniorimportance: should knowfreq 20%

basics

~20 s

includeFlat expects ../common to exist next to the root. On CI only the root repo was cloned, so the sibling is missing. Fix by checking out the sibling beside the root, or restructure to include/composite build.

open as a page

When would you choose includeFlat over a hierarchical include layout or a composite build (includeBuild)? What are the trade-offs?

level: seniorimportance: should knowfreq 25%

basics

~10 s

Use includeFlat when projects are checked out side-by-side as separate folders but must share one build. Use hierarchical include for a single repo, and composite builds (includeBuild) for independent builds with their own settings.

open as a page

At what point in the Gradle lifecycle is the project graph assembled from include() calls, and why does that ordering matter?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The project graph is built during the settings (initialization) phase, when Gradle evaluates settings.gradle.kts and runs every include(). This happens before any build.gradle.kts is configured, so the full set of projects is known before configuration starts.

open as a page

When you set project(':lib').projectDir = file('libs/lib'), how is that path resolved and what common mistakes break it?

level: seniorimportance: should knowfreq 25%

basics

~10 s

file('libs/lib') resolves relative to the settings file's directory (rootDir). Common breakages: setting projectDir before include, pointing at a non-existent or wrong directory, or assuming it also changes the logical path.

open as a page

A teammate puts shared subproject conventions (Java version, common dependencies) directly in settings.gradle.kts. Why is that wrong, and where should they go?

level: seniorimportance: should knowfreq 40%

basics

~10 s

Settings can't configure projects — it only declares structure. Shared conventions belong in the root build script (subprojects/allprojects) or, better, in convention plugins applied per project.

open as a page

When you include(":services:auth") but never include(":services"), does a configurable :services project exist?

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

Including only :services:auth creates the auth project and a :services container in the hierarchy, but :services has no build script by default, so it isn't a project you configure. To configure :services, include it explicitly.

open as a page

In a large monorepo, what's your strategy for using projectDir customization, and what governance concerns does decoupling logical paths from disk layout raise?

level: principalimportance: nice to knowfreq 12%

basics

~10 s

Use projectDir sparingly and consistently — e.g. a single documented rule like 'flat paths, folders grouped under services/'. Avoid ad-hoc per-project overrides because they make the build hard to navigate and reason about.

open as a page