IntelliJ IDEA in Practice: Your First Java Project, Compile and Run
IDEA is not "Notepad with colours". It is a machine that performs all the mechanical steps in order for you: save a file and it compiles; change a dependency and it fetches from a repository; press Run and it turns .java into .class, assembles the classpath, starts a JVM and calls main. This article covers three things: installing and finding your way around (the UI and six terms), getting something to run (project layout / SDK / run configuration), and spotting the moment the tool is lying to you (caches, encodings, red code, a greyed-out Run button).
writing Java without an IDE is like hand-writing a newspaper and expecting the print shop to notice your revisions — you rewrite three pages, but the shop still has yesterday's type set, so tomorrow's edition prints the old text. IDEA's job is exactly "re-typeset after the draft changes": before every Run it incrementally re-typesets only the pages you touched (compilation) and hands the finished copy to the reader (the JVM). Once that clicks, oddities like "I changed the code but the output is stale" become obvious at a glance.

After this article you should be able to answer:
- What are Project / Module / SDK / Artifact, and which one decides which JDK I use?
- When I press Run, what does IDEA do first, and what second? Which folder does the output land in?
- Why is red code not necessarily my fault? Why did the Run button suddenly turn grey?
IntelliJ IDEA is the most widely used IDE among Java developers. It ships in two editions, and choosing wrong adds pointless detours to your learning:
| Aspect | Community | Ultimate |
|---|---|---|
| Price | Free | Paid (trials and educational licenses available) |
| Plain Java / Maven / Gradle | Supported | Supported |
| Spring / Spring Boot | None (plugins needed) | Full built-in support |
| Database tools, HTTP Client | None | Built in |
| Web (Servlet / JSP / Thymeleaf) | None | Built in |
| Best for | Learning Java basics, plain Java projects | Spring enterprise development |
The conclusion is simple: the rest of this course involves heavy Spring work, so use Ultimate; if you are only learning Java syntax for now, Community is more than enough.
IDEA ships an official Chinese Language Pack. Install it via Settings → Plugins, search Chinese, then restart. An honest recommendation:
- While learning, a localized UI reduces the frustration of not finding menus
- But shortcuts, error messages and Stack Overflow answers are all in English, so in the long run the English UI pays off more
- A middle ground: start in your own language, and immediately note the English menu name for every unfamiliar concept
in either language, the command palette is Ctrl+Shift+A (Cmd+Shift+A on Mac). When you forget where a feature lives, type its English name and search — ten times faster than hunting through menus.
Beginners get lost in the pile of terms inside Project Structure. They actually form a chain from largest container to final product:
| Term | In one sentence | Everyday analogy |
|---|---|---|
| Project | The top-level container managing a set of modules and config | A building |
| Module | The unit that actually holds source code; there can be several | Each apartment in the building |
| SDK | The software development kit — here, the JDK the project uses | Each apartment's utility standard |
| Library | A set of external dependency jars | Appliances you bring in |
| Facet | Marks what technology a module uses (e.g. Web) | Labeling a room's purpose |
| Artifact | The packaged jar/war and where it is written | The finished product after renovation |
In one sentence: one Project can contain several Modules; each Module binds an SDK, references Libraries, may carry Facets, and finally produces an Artifact.
After creating a project, the root gains a .idea/ folder and a *.iml file, holding:
.idea/├── workspace.xml # window layout, recent files, transient run state├── modules.xml # the module list├── misc.xml # project-level JDK config (often worth sharing)└── *.iml # module definition (JDK, dependencies, output dir)Should they be committed to Git? There is no single answer — it depends on the content:
workspace.xmlholds your personal window state, so it must be excluded; committing it causes daily conflictsmisc.xml/*.imlmay contain the JDK version and module dependencies, so they can be committed when the team wants consistency- The easiest approach: ignore the entire
.idea/in.gitignoreand let everyone regenerate it from Maven'spom.xml
to "commit some, ignore the rest", write .idea/* in .gitignore then re-include with !.idea/misc.xml — ignoring the whole folder and whitelisting a file is cleaner than excluding entry by entry.
IDEA never explains these nouns, yet each one maps to a concrete thing. Play a round: the left column is what you click in a menu, the right column is what it actually does — a wrong pick explains the gap on the spot:

Follow the wizard: File → New → Project, choose Java, pick "IntelliJ" as the build system (skip Maven for now to reduce variables), and choose JDK 17 or 21 from the dropdown — if the dropdown is empty you have no JDK yet; click Add SDK → Download JDK and let IDEA fetch one for you.
Once created, right-click the src directory, go New → Package (enter com.example.demo), then on the package New → Java Class (enter Hello), and write:
package com.example.demo;public class Hello { public static void main(String[] args) { // psvm is an IDEA live template: type psvm and press Enter for this line String name = "Spring"; int year = 2024; System.out.println("Hello, " + name + "!"); System.out.printf("The Spring Framework was born in %d%n", 2003); System.out.println("Current study year: " + year); }}A small green triangle appears to the left of the line numbers; click it and choose Run 'Hello.main()', or just press Shift+F10. The Run window prints the result.
The first time you press Run, IDEA creates a "Run/Debug Configuration" for you, recording how to run this class. Open Run → Edit Configurations and you will see these key items:
| Field | Purpose | Common beginner mistake |
|---|---|---|
| Main class | Which class's main to start from | Picking the wrong class, causing "main not found" |
| Program arguments | Values passed to String[] args | Swapped with VM options |
| VM options | JVM flags such as -Xmx512m | Putting -D system properties in args |
| Working directory | The process's working directory | Relative paths then cannot find files |
| Use classpath of module | Which module's classpath to use | Choosing the wrong module in a multi-module build |
Program arguments go to your program; VM options go to the JVM. Swapping them is a frequent incident: putting --spring.profiles.active=dev into VM options makes it fail as a JVM argument at startup.
Beginners think "Run means run the Java code", but IDEA first finishes the "compile" step behind the scenes. The full chain is:
- Incremental compile: IDEA recompiles only the
.javafiles you changed instead of everything, so the second run is always fast - Write bytecode: output goes to the output directory —
out/for a non-Maven project,target/classesfor a Maven one - Class loading: a JVM starts and a ClassLoader reads the main class's bytecode
- Run main: the JVM invokes the entry method and the program runs
a revised draft must be re-printed. Your manuscript (.java) changed, but the copy readers hold (.class) was set from yesterday's type — unless both sit on the same table, you can never spot the difference. Incremental compilation is "re-typeset only the pages you touched", which is why it is fast; Rebuild Project re-typesets the whole paper, worth doing only when you suspect the type case itself is dirty. This same analogy explains two traps in Section 7: "code changes that never take effect" and "classes disappear after deleting out/".
This is the pair most often confused; keep them straight:
| Aspect | out/ (IntelliJ build) | target/ (Maven build) |
|---|---|---|
| Who creates it | IDEA's built-in builder | Maven plugins |
| Triggered by | Build Project / Run | mvn compile / mvn package |
| Layout | out/production/<module>/ | target/classes/ |
| Deletable | Yes, rebuilt on demand | Yes, emptied by mvn clean |
| Common trap | Deleting out without rebuilding → class not found | Editing code without clean → you run the old build |
That last row of the table is the hardest one to notice yourself. This animation shows how two copies of your bytecode come to coexist — watch frame 5: what Run reads and what the shell reads may not be the same file:

There is also a real number hiding in IDEA's build: its builder runs in a separate process that gets 700 MiB by default. As the project grows and annotation processors pile on, the build output starts showing java.lang.OutOfMemoryError: Java heap space — note that it crashes in the build phase, while your program itself runs perfectly. Drag that number:
- Course examples and one or two modules never reach the ceiling
- When they do, the symptom is that line in the build output: java.lang.OutOfMemoryError: Java heap space
- The build crashes, not the runtime — the program you launched is fine, which is why people fix the wrong thing
- No machine restart needed: this number exists exactly for that case
Build Project vs Rebuild is equally worth remembering:
Build Project(Ctrl+F9): incremental, compiling only changed files — your everyday choiceRebuild Project: wipes all output and recompiles everything — use it only when you suspect a stale cache; slow but clean
when you change code and still see old results, nine times out of ten the incremental build cache is stale. Build → Rebuild Project fixes it — no need to restart your machine.
Now spread that chain out as a single-step run. Six events on the left after you click Run, on the right the variables and the call stack as they change — pay attention to beats 2 and 4: the grey button in Section 11 comes from beat 2, and the package does not exist in Section 12 comes from beat 4:
Click the green Run triangle # it starts from a Run/Debug ConfigurationIDEA validates: SDK / module / main? # fail any of them and the button is greyIncremental compile of the changed files # into out/production or target/classesAssemble the classpath: module deps + jars # Use classpath of module decides the contentFork a JVM: java -cp ... Hello # this is where IDEA steps outLoad the class, call Hello.main(String[]) # your code finally starts| config source | Run → Edit Configurations |
| existing config | Hello.main() |
| first click | creates one automatically |
click RunDebugging is IDEA's real productivity engine. Press Shift+F9 to start in Debug mode — same effect as Run, except it stops at breakpoints.
Click in the gutter next to a line to place one; a red dot means the breakpoint is set. Then memorize this set:
public static int sum(int[] nums) { int total = 0; for (int i = 0; i < nums.length; i++) { total += nums[i]; // ← set a conditional breakpoint here: i == 3 } return total;}- Conditional breakpoint: right-click the breakpoint, set Condition to
i == 3, and it stops only then — a lifesaver on the thousandth loop iteration - Step Over (F8): execute the current line without entering method calls
- Step Into (F7): step into the called method
- Step Out (Shift+F8): jump back out to the caller
- Evaluate Expression (Alt+F8): a pop-up calculator that evaluates any expression live, e.g.
nums.length - Watches: add variables to the watch list to see their latest value on every stop
Here is one rarely used but superb trick: while stopped you can double-click a variable in the Variables panel, press Enter, and change its value, then Resume. To test "what if this parameter were empty", you don't change and rerun the code — just change the value.
when debugging multithreaded code, don't chase threads with Step Over — they jump around. Use the breakpoint's Suspend: Thread setting instead, and add a log output (Breakpoint → More → Evaluate and log) to print traces without blocking the thread.
Real projects are almost always Maven projects. Open one with File → Open and pick the directory containing pom.xml; IDEA recognizes it as a Maven project and imports it automatically.
After import a Maven tool window appears on the right. A few frequent actions to know:
- Automatic sync: when
pom.xmlchanges, a small icon appears at the top right promptingReload; one click fetches the new dependencies - Reload All Maven Projects: the refresh button at the top-left of the Maven panel, equivalent to re-reading all poms and re-resolving dependencies
- Yellow warnings: a yellow squiggle under a dependency usually means "this artifact is not in the local repo" or "the version is overridden by dependencyManagement"; hover for the explanation
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- Common cause of the yellow mark: the parent POM already manages the version via dependencyManagement, so no version is needed here --></dependency>Trap: when everything is red in IDEA yet mvn clean package clearly succeeds, IDEA's Maven index is usually broken. Run Maven panel → Reload All Maven Projects; if that fails, File → Invalidate Caches → Invalidate and Restart. Always trust the command-line build result — IDEA's red is only its own view.
The reverse reading matters just as much: every checkbox in File → New → Project ends up as a <dependency> in pom.xml. Tick them once and compare with the generated file, and you will know what IDEA wrote on your behalf — so that next time it writes nothing, you can do it yourself:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version> <!-- 版本由 BOM 统管,子依赖不写 version -->
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>demo-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>| Symptom | Root cause | Fix |
|---|---|---|
| Every Java class is red and cannot run | Project SDK not configured | File → Project Structure → SDK, pick a JDK |
| Chinese output is mojibake | File encoding is GBK, not UTF-8 | Set everything to UTF-8 in Settings → File Encodings |
package xxx does not correspond to the file path | Package name mismatches the folder path | Mark src as a Source Root, or fix the package name |
| Code changes simply don't take effect | Stale cache / stale output | Invalidate Caches and Restart or rebuild |
Lombok @Data compiles but getters are missing | The Lombok plugin is not installed | Install Lombok in Plugins and enable annotation processing |
Startup fails with Port 8080 was already in use | A previous process never exited | Kill the process, or change the port |
The fastest way to find a port hog is one command:
# Windows: who is holding port 8080?netstat -ano | findstr :8080# macOS / Linuxlsof -i :8080- The trailing number is the process PID; end it via the task manager or
taskkill /PID <pid> /F - Don't just reboot — that hides the problem and it returns next time
Warning: Settings → File Encodings only affects the current project. Set Project Encoding and Default encoding for properties files to UTF-8 too, and check Transparent native-to-ascii conversion, otherwise Chinese comments in application.properties still come out garbled.
| Action | Windows | macOS |
|---|---|---|
| Run / Debug | Shift+F10 / Shift+F9 | Ctrl+R / Ctrl+D |
| Build project | Ctrl+F9 | Cmd+F9 |
| Find anything (command palette) | Ctrl+Shift+A | Cmd+Shift+A |
| Search files / classes globally | Ctrl+Shift+N | Cmd+Shift+O |
| Generate code (getters, constructor) | Alt+Insert | Cmd+N |
| Rename (safe refactor) | Shift+F6 | Shift+F6 |
| Reformat code | Ctrl+Alt+L | Cmd+Opt+L |
| Go to definition / implementations | Ctrl+B / Ctrl+Alt+B | Cmd+B / Cmd+Opt+B |
| Step in debugging | F8 / F7 | F8 / F7 |
| The Alt key (multi-cursor, column select) | Hold Alt and drag | Hold Opt and drag |
don't memorize all of these at once. First make "run, debug, command palette, global search, generate code" muscle memory — that alone doubles your daily speed; look the rest up when needed.
The first lab is the animated version of Section 4: how many steps sit between saving and a live process. "Compile output" shows how target/classes gets filled; "hot reload" shows why editing one string does not need a restart; "debug" maps the six moves from Section 5 onto what happens inside the process:
The second answers a more fundamental question: how does the JVM get your class into memory? The "class loading" step shows it resolving the fully qualified name (com.example.demo.Hello) into a path on disk — which is exactly where the Package ... does not correspond to the file path error in Section 12 comes from: a package name is not decoration, it is part of a path.
The third targets "my code is fine but it says class not found". On "ClassNotFoundException" you watch it walk each entry on the classpath; whenever target/classes is empty or stale, any java command ends up on this dead street:
The fourth prepares you for the next article: a Spring Boot startup has several more steps than a bare main. A Runner is Boot's hook for "do something once the app is up" (CommandLineRunner / ApplicationRunner), and the last experiment shows how to actually read that wall of text when startup fails:
The fifth lab covers the most infuriating class of problem in this chapter: works in the IDE, dies in the terminal — because the two are not using the same JDK at all. Open "who wins" to watch the shell walk PATH in order, then "several JDKs installed" to see IDEA's Project SDK and the terminal's java drift apart:
Time to type. This console is wired to the same in-browser kernel and every reply is computed there — start with lab idebuild build, then work down:
run lab idebuild run and lab idebuild debug back to back — the two outputs differ by a single JVM parameter. That one parameter is what makes all six debugging moves from Section 5 possible.
And keep this comparison in mind. The left column is what beginners do every day, the right column is the same task done correctly — the difference is not speed but whether the tool fools you:

A greyed-out Run triangle never means "IDEA is broken"; it means IDEA has decided pressing it would certainly fail. Pick the module state on the left and the SDK state on the right, and the panel tells you exactly where to click to recover:


No matching result
the easiest cell to miss is no-main — IDEA only draws a green triangle for an entry point it recognises. The main signature is a hard contract: public, static, void, parameter String[]; fail any one of the four and there is no Run gutter icon.
| Error text (excerpt) | Real cause | 30-second fix | Dig deeper in | |
|---|---|---|---|---|
Class 'Hello' is public, should be declared in a file named 'Hello.java' | Public class name and file name differ (case counts as a difference) | Rename the file, not the class; they must match character by character | #1 Section 4 | |
Package com.example.demo does not correspond to the file path 'com/example/hello' | Folder case disagrees with the package, or src is not marked as Sources Root | Right-click the folder → Mark Directory as → Sources Root, then re-check each package level | Section 2 · sandbox above | |
Exception in thread "main" java.lang.ClassNotFoundException: com.example.demo.Hello (fails in the terminal, fine in IDEA) | target/classes was never compiled, or your hand-typed -cp omits the output directory | Run mvn compile (or Ctrl+F9 in IDEA), then java -cp target/classes com.example.demo.Hello | #2 Maven lifecycles | |
java: 程序包 xxx 不存在 / Cannot resolve symbol 'springframework' | pom changed but was never Reloaded, so dependencies are not on the module classpath | Maven panel → Reload All Maven Projects; if still broken, File → Invalidate Caches | Section 6 | |
| Run / Debug buttons greyed out | No Project SDK, no recognised main, or the module is not marked as a source root | Work through the three cases in the Section 11 sandbox; the usual one is Project Structure → SDK | Section 11 | |
Shift+F10 triggers something else instead of Run | Another program grabbed the key (NVIDIA driver hotkey, input method, screenshot tool) | Resign it under Settings → Keymap, searching Run…; or confirm the config is fine by clicking the gutter triangle | Section 8 hotkey trap | |
Console shows mojibake like 锟斤拷 or ?????? | Source encoding, compiler -encoding and console charset disagree | Set all of Settings → File Encodings to UTF-8; add -Dfile.encoding=UTF-8 to VM options | #35 logging | |
Port 8080 was already in use | The previous process is still alive and holding the port | `netstat -ano \ | findstr :8080 for the PID, then taskkill /PID <pid> /F` | Section 7 |
ClassNotFoundException almost never appears inside IDEA but constantly appears in the terminal — because IDEA runs against its own build output (possibly out/production/...) while your hand-typed command points at target/classes. Two output directories drifting apart is the root of "this class is found sometimes and not others".
That trap deserves a real stack to close it. Notice the guilty frame is not the top line, and not the Exception in thread sentence either:
You compiled with Ctrl+F9 and Run works; then you type java -cp target/classes com.example.demo.Hello in the terminal and it throws.
Goal: reproduce and repair the three classic "IDEA tricked me" scenes.
package com.example.demo;public class DebugLab { public static int sum(int[] nums) { int total = 0; for (int i = 0; i < nums.length; i++) { total += nums[i]; // ← breakpoint here, condition i == 3 } return total; } public static void main(String[] args) { int[] data = {1, 2, 3, 4, 5, 6}; int result = sum(data); System.out.println("sum = " + result); // ← breakpoint here, then set result to 0 System.out.println("result * 2 = " + (result * 2)); }}Do these three things in order, writing one sentence of observation for each:
- Put a conditional breakpoint on line 8 with condition
i == 3, start withShift+F9, and confirm it stops only on the fourth iteration. - Stopped at line 15, double-click
resultin the Variables panel, change it to0, then Resume. Expected output: the second line printsresult * 2 = 0— proof that editing a value really changes execution. - With
Evaluate Expression(Alt+F8), computedata.length + resultwithout touching the code, then evaluatenums[0]outside the loop context and observe what exception surfaces.
Done when: you can say why a conditional breakpoint beats println debugging, and how changing a runtime value differs from editing code and rerunning.
Three small edits, each reproducing one frequent failure — the skill being practised is spotting which layer broke:
- Keep
package com.example.demo;but move the file to the root ofsrc. You will observe IDEA complaining that the package does not match the path, and the "fully qualified name → disk path" mapping from the Section 10 lab breaking on the spot. - Delete the whole
outortargetdirectory, do not Build, and runjava -cp target/classes com.example.demo.DebugLabfrom the terminal. You will observeClassNotFoundException; onemvn compileorCtrl+F9restores it. - Turn off auto-save under
Settings → Appearance & Behavior → System Settings, edit a print line without pressingCtrl+S, then hit Run. You will observe IDEA's prompt, and why it synchronises changes to disk before compiling.
Tip: after variant 3 you will internalise that "save ≠ compile ≠ run" are three separate events.
Create a two-module Maven project that links this article with #2:
lab-ide/ <- parent pom, packaging=pom├── lab-core/ <- SumUtil plus one custom exception└── lab-app/ <- depends on lab-core, contains main and a CommandLineRunnerRequirements: ① import it with File → Open on the parent directory (not New Project) and confirm two modules appear in the Maven panel; ② leave a deliberate null dereference in lab-app's main and use an exception breakpoint (Run → View Breakpoints → Java Exception Breakpoints) so IDEA stops at the throw site instead of you stepping around looking for it; ③ add a CommandLineRunner printing one statistics line after startup; ④ run it once with Run and once with Debug and write down three concrete differences; ⑤ finally launch it manually from the terminal with java -cp and notice what IDEA had been assembling for you.
Acceptance checklist: mvn clean install is green; Debug halts exactly at the exception; the manual java -cp launch also works — meaning you genuinely know how that classpath was built.
among Project / Module / SDK / Artifact, which decides "which JDK do I use" and which decides "what do I package into"?
who writes out/production/<module> and who writes target/classes? Why do they drift apart?
Run and Debug share the same chain except for one extra step — which step, what capability does it buy, and at what cost?
who receives Program arguments and who receives VM options? What goes wrong if you put --spring.profiles.active=dev in the wrong box?
IDEA shows red everywhere yet mvn clean package succeeds. Whom do you believe, and why?
save belongs to the disk, compile to bytecode, run to the classpath, debug to breakpoints — four layers, four switches; when something breaks, first ask which layer you are standing in.
this article straightened out IDEA's three main threads — structure (the layered relationship of Project / Module / SDK), running (incremental compile into out/ or target/, then a JVM loads and runs main), and debugging (breakpoints, stepping, evaluation, changing values). Combined with dodging the six beginner traps, your development environment is now stable and you can focus on writing code.