IDE Setup Best Practices
Overview
Proper IDE configuration is the foundation of developer productivity. Modern IDEs provide intelligent code completion, automated refactoring, integrated debugging, and real-time error detection that transform development workflows. This guide covers IDE setup for IntelliJ IDEA (JVM languages) and VS Code (polyglot development), including essential plugins, code style configurations, debugging setups, and local development environment orchestration.
Shared IDE configuration eliminates whitespace conflicts in code reviews and ensures consistent code formatting across teams. Version-controlled run configurations enable all developers to execute applications and tests identically. Features like live templates and keyboard-driven workflows reduce repetitive tasks from hours to seconds.
Core Principles
- Shared Configuration: Version control IDE settings (.editorconfig, code style files, run configurations)
- Consistent Formatting: Automated formatting prevents whitespace conflicts and style debates
- Essential Plugins Only: Plugin bloat degrades performance; install only what provides value
- Keyboard-Driven Workflows: Mastering shortcuts dramatically increases productivity
- Live Templates: Automate repetitive code patterns with minimal keystrokes
- Static Analysis Integration: Enable real-time linting and inspections
- Environment Parity: Match local development environment to production (same DB versions, runtime versions)
IntelliJ IDEA
IntelliJ IDEA is JetBrains' IDE for JVM languages, providing deep Spring Boot integration, advanced refactoring, and framework-aware code intelligence. See Spring Boot documentation for framework-specific features.
Recommended Edition
IntelliJ IDEA Ultimate (required, not Community) provides:
- Spring Boot Integration: Dependency injection navigation, auto-configuration insights, bean lifecycle visualization
- Database Tools: Visual query builder, schema navigation, migration execution, integrated SQL console
- HTTP Client: Test REST APIs without external tools (alternative to Postman)
- Profiler Integration: CPU/memory profiling with code-level granularity
- Advanced Refactoring: Safe rename across Spring beans, extract microservice, convert to functional
Ultimate Edition's Spring Boot support includes understanding Spring-specific annotations (@Autowired, @Bean, @Configuration) to provide context-aware code completion and navigate from injection points to bean definitions instantly - essential for large Spring Boot applications with complex dependency graphs.
Essential Plugins
Install via IDE: Settings → Plugins → Marketplace → Search → Install
Required Plugins:
├── Lombok # Annotation processing for boilerplate reduction
├── SonarLint # Real-time static code analysis
├── CheckStyle-IDEA # Code style enforcement (configurable rules)
├── GitLab Integration # MR operations, CI/CD status
├── Docker # Dockerfile editing, container management
└── OpenAPI Specifications # OpenAPI/Swagger YAML editor with validation
Recommended Plugins:
├── Key Promoter X # Learn keyboard shortcuts (shows popup when using mouse)
├── Rainbow Brackets # Color-matched bracket pairs
├── String Manipulation # Case conversion, encoding/decoding
├── .ignore # .gitignore template generation
└── Material Theme UI # Modern UI theme
Lombok processes annotations like @Data, @Builder, @Slf4j to generate getters, setters, builders, and loggers at compile time - the plugin enables IDE recognition of these generated methods. SonarLint applies 600+ code quality rules in real-time, highlighting bugs, code smells, and security vulnerabilities as you type. CheckStyle enforces team-defined coding standards (naming conventions, import ordering, brace placement). GitLab Integration displays MR status, allows creating MRs, and shows CI/CD pipeline results without leaving the IDE.
Code Style Configuration
Shared code style ensures identical formatting across all developers. Commit .idea/codeStyles/Project.xml to version control so settings apply automatically when teammates clone the repository.
Import Configuration
<!-- .idea/codeStyles/Project.xml -->
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JavaCodeStyleSettings>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="java" withSubpackages="true" static="false" />
<package name="javax" withSubpackages="true" static="false" />
<emptyLine />
<package name="org.springframework" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="true" />
</value>
</option>
</JavaCodeStyleSettings>
</code_scheme>
</component>
Setting CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND to 999 prevents wildcard imports (import java.util.*). Wildcard imports hide which specific classes are used and break when new classes with conflicting names are added to imported packages. Explicit imports (import java.util.List) make dependencies clear and prevent name collision issues.
The import layout groups organize imports into logical sections separated by blank lines: Java standard library, Spring framework, application code, then static imports. This grouping improves readability and reduces merge conflicts when multiple developers add imports to the same file.
EditorConfig
EditorConfig defines formatting rules recognized by all major IDEs automatically - no IDE-specific configuration needed. Place at repository root:
# .editorconfig
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.java]
indent_style = space
indent_size = 4
max_line_length = 120
[*.{ts,tsx,js,jsx}]
indent_style = space
indent_size = 2
max_line_length = 100
[*.{yml,yaml}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
end_of_line = lf enforces Unix-style line endings even on Windows, preventing line ending conflicts in Git. trim_trailing_whitespace eliminates invisible whitespace that creates diff noise. Language-specific indent sizes match community conventions: 4 spaces for Java, 2 for TypeScript/YAML. Markdown files disable trailing whitespace trimming because trailing spaces create line breaks in Markdown syntax.
Live Templates
Live templates expand abbreviations (type abbreviation, press Tab) to full code blocks with cursor placeholders. Templates use $VARIABLE$ syntax - after expansion, Tab cycles through placeholders for customization. This automates repetitive patterns while enforcing team standards.
Create Template Group
Settings → Editor → Live Templates → Add Template Group "Banking"
Payment Processing Template
Template: payment
Abbreviation: payment
Description: Payment processing method with validation, fraud check, audit
Template text:
public PaymentResult process${NAME}Payment($PARAMS$) {
log.info("Processing ${NAME} payment for user {}", userId);
// Validate payment
validatePayment(payment);
// Check fraud
fraudService.checkFraud(payment);
// Execute payment
PaymentResult result = executePayment(payment);
// Audit log
auditLogger.logPaymentProcessed(payment, result);
return result;
}
Type payment, press Tab, cursor jumps to ${NAME} - enter payment type ("Transfer", "Bill"). Tab again moves to $PARAMS$ for parameter definition. This template encodes the standard pattern: validation → fraud checking → execution → audit logging. All payment processing methods follow this structure, ensuring critical steps (fraud check, audit) are never forgotten.
Test Method Template
Template: test
Abbreviation: test
Description: JUnit 5 test method with Given-When-Then structure
Template text:
@Test
void should${NAME}_when${CONDITION}() {
// Given
$GIVEN$
// When
$WHEN$
// Then
$THEN$
}
This template enforces Given-When-Then structure. Method names follow shouldX_whenY convention, making test intent explicit: shouldRejectPayment_whenInsufficientFunds(). The three-section structure separates test setup (Given), action under test (When), and verification (Then), improving test readability and maintainability. See Java Testing Guidelines for detailed testing patterns.
Keyboard Shortcuts
Master these shortcuts to eliminate mouse usage and accelerate workflows:
| Action | Windows/Linux | Purpose |
|---|---|---|
| Navigation | ||
| Go to class | Ctrl + N | Jump to any class by name (supports fuzzy search) |
| Go to file | Ctrl + Shift + N | Jump to any file (resources, configs, etc.) |
| Go to symbol | Ctrl + Alt + Shift + N | Jump to method/field across project |
| Recent files | Ctrl + E | Switch between recently opened files |
| Navigate back/forward | Ctrl + Alt + ←/→ | Navigate cursor history (like browser back/forward) |
| Editing | ||
| Duplicate line | Ctrl + D | Duplicate current line or selection |
| Delete line | Ctrl + Y | Delete current line (no copy to clipboard) |
| Move line up/down | Alt + Shift + ↑/↓ | Move line while maintaining indentation |
| Format code | Ctrl + Alt + L | Apply code style formatting |
| Optimize imports | Ctrl + Alt + O | Remove unused imports, organize order |
| Refactoring | ||
| Rename | Shift + F6 | Safe rename (updates all references) |
| Extract method | Ctrl + Alt + M | Extract selected code to new method |
| Extract variable | Ctrl + Alt + V | Extract expression to variable |
| Inline | Ctrl + Alt + N | Inline variable/method (opposite of extract) |
| Code Generation | ||
| Generate | Alt + Insert | Generate getters/setters/constructors/toString |
| Override methods | Ctrl + O | Override superclass methods |
| Implement methods | Ctrl + I | Implement interface methods |
| Search | ||
| Find in path | Ctrl + Shift + F | Project-wide text search |
| Replace in path | Ctrl + Shift + R | Project-wide find and replace |
| Run/Debug | ||
| Run | Shift + F10 | Run current configuration |
| Debug | Shift + F9 | Debug current configuration |
| Toggle breakpoint | Ctrl + F8 | Add/remove breakpoint at current line |
| Evaluate expression | Alt + F8 | Evaluate expression during debugging |
The "Go to class/file/symbol" shortcuts use fuzzy matching: type "PaSe" to find "PaymentService". This eliminates scrolling through package hierarchies. Extract method refactoring (Ctrl + Alt + M) automatically detects required parameters and return types, making code organization effortless.
Run Configurations
Shared run configurations ensure all developers execute applications identically. Commit to .idea/runConfigurations/ for version control.
Spring Boot Application
<!-- .idea/runConfigurations/PaymentService.xml -->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="PaymentService" type="SpringBootApplicationConfigurationType">
<option name="SPRING_BOOT_MAIN_CLASS" value="com.bank.payment.PaymentApplication" />
<module name="payment-service" />
<option name="ACTIVE_PROFILES" value="dev" />
<option name="VM_PARAMETERS" value="-Xmx512m -Dspring.profiles.active=dev" />
<option name="PROGRAM_PARAMETERS" value="" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
Activating the dev profile loads application-dev.yml for development-specific configuration (local database URLs, debug logging). The -Xmx512m heap limit matches production container constraints, surfacing memory issues during development rather than production. The Make option compiles code before running, ensuring latest changes execute.
JUnit Tests
<!-- .idea/runConfigurations/AllTests.xml -->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All Tests" type="JUnit">
<module name="payment-service" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
The -ea flag enables assertions. Java disables assertions by default for performance, but tests should validate assert statements: assert result.isValid() : "Result validation failed". Assertions provide runtime invariant checking that complements unit tests.
Remote Debugging
Remote debugging connects your IDE to applications running elsewhere (Docker, Kubernetes, remote servers), enabling full debugging capabilities (breakpoints, variable inspection, step-through) against live environments.
Enable Debug Port
Start Java applications with JDWP (Java Debug Wire Protocol) enabled:
# Start with debug port enabled
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 \
-jar payment-service.jar
# Docker: expose debug port
docker run -p 8080:8080 -p 5005:5005 payment-service
# Kubernetes: port forward to local machine
kubectl port-forward pod/payment-service-abc123 5005:5005
The -agentlib:jdwp parameters: transport=dt_socket uses TCP sockets, server=y makes the JVM listen for debugger connections, suspend=n starts the application normally without waiting (use suspend=y to pause at startup for debugging initialization code), address=*:5005 listens on all interfaces on port 5005.
Kubernetes port-forwarding creates a tunnel from localhost:5005 to the pod's port 5005, making the remote debug port appear local. See Docker and Kubernetes documentation for container debugging patterns.
Debug Configuration
<!-- .idea/runConfigurations/RemoteDebug.xml -->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Remote Debug" type="Remote">
<option name="USE_SOCKET_TRANSPORT" value="true" />
<option name="SERVER_MODE" value="false" />
<option name="SHMEM_ADDRESS" />
<option name="HOST" value="localhost" />
<option name="PORT" value="5005" />
<option name="AUTO_RESTART" value="false" />
<RunnerSettings RunnerId="Debug">
<option name="DEBUG_PORT" value="5005" />
<option name="LOCAL" value="false" />
</RunnerSettings>
</configuration>
</component>
After port-forwarding and starting this configuration (Shift + F9), set breakpoints in your IDE - execution pauses when the remote application hits those lines. This enables debugging environment-specific issues that don't reproduce locally.
VS Code
Visual Studio Code is a lightweight, extensible editor excelling at polyglot development (Java, TypeScript, Python, Go) with faster startup and lower memory footprint than IntelliJ. It's particularly strong for frontend development (React, Angular) with excellent Git integration.
Essential Extensions
Create .vscode/extensions.json and commit to version control. VS Code automatically recommends these extensions when teammates open the project:
{
"recommendations": [
// Java
"vscjava.vscode-java-pack",
"vscjava.vscode-spring-boot-dashboard",
"redhat.java",
"vscjava.vscode-maven",
// TypeScript/JavaScript
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss",
// Git
"eamodio.gitlens",
"gitlab.gitlab-workflow",
// Docker
"ms-azuretools.vscode-docker",
// Database
"cweijan.vscode-database-client2",
// API
"42crunch.vscode-openapi",
"humao.rest-client",
// Code Quality
"sonarsource.sonarlint-vscode",
"streetsidesoftware.code-spell-checker",
// Productivity
"gruntfuggly.todo-tree",
"usernamehw.errorlens",
"wayou.vscode-todo-highlight"
]
}
GitLens provides inline Git blame annotations, commit history visualization, and repository comparisons. ErrorLens displays linting errors inline (next to code) rather than requiring hovering, enabling faster error identification. TODO Tree highlights TODO/FIXME comments across the project for quick navigation.
Settings Configuration
Commit .vscode/settings.json to enforce team-wide editor behavior:
{
// .vscode/settings.json
// Editor
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true,
"source.fixAll.eslint": true
},
"editor.rulers": [100, 120],
"editor.tabSize": 2,
"editor.insertSpaces": true,
// Java
"java.configuration.updateBuildConfiguration": "automatic",
"java.saveActions.organizeImports": true,
"java.format.settings.url": ".vscode/eclipse-formatter.xml",
// TypeScript
"typescript.updateImportsOnFileMove.enabled": "always",
"typescript.preferences.importModuleSpecifier": "relative",
// Prettier
"prettier.singleQuote": true,
"prettier.trailingComma": "es5",
"prettier.printWidth": 100,
// ESLint
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
// Files
"files.exclude": {
"**/.git": true,
"**/.DS_Store": true,
"**/node_modules": true,
"**/target": true
},
"files.watcherExclude": {
"**/target/**": true,
"**/node_modules/**": true
},
// Search
"search.exclude": {
"**/node_modules": true,
"**/target": true,
"**/build": true,
"**/.git": true
}
}
formatOnSave and organizeImports eliminate manual formatting - saving a file automatically applies Prettier/ESLint rules and removes unused imports. editor.rulers displays vertical lines at 100 and 120 characters for visual line length guidance. Excluding node_modules and target from search and file watching improves performance dramatically in large projects.
Launch Configurations
Define application launch and debugging in .vscode/launch.json:
{
// .vscode/launch.json
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "PaymentApplication",
"request": "launch",
"mainClass": "com.bank.payment.PaymentApplication",
"projectName": "payment-service",
"args": "",
"vmArgs": "-Dspring.profiles.active=dev",
"env": {
"SPRING_PROFILES_ACTIVE": "dev"
}
},
{
"type": "java",
"name": "Debug (Attach)",
"request": "attach",
"hostName": "localhost",
"port": 5005
},
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/src/index.ts",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
}
]
}
The "Debug (Attach)" configuration connects to remote debug ports (same as IntelliJ remote debugging). The Node.js configuration runs a preLaunchTask to compile TypeScript before launching, ensuring the latest code executes.
Tasks
Tasks define common build/test operations in .vscode/tasks.json:
{
// .vscode/tasks.json
"version": "2.0.0",
"tasks": [
{
"label": "Gradle: build",
"type": "shell",
"command": "./gradlew build -x test --no-daemon",
"group": "build",
"problemMatcher": []
},
{
"label": "Gradle: test",
"type": "shell",
"command": "./gradlew test --no-daemon",
"group": "test",
"problemMatcher": []
},
{
"label": "npm: install",
"type": "npm",
"script": "install",
"group": "build"
},
{
"label": "npm: test",
"type": "npm",
"script": "test",
"group": "test"
}
]
}
Tasks execute via Command Palette (Ctrl + Shift + P → "Tasks: Run Task"). The problemMatcher parses command output to highlight errors in the editor. Tasks can serve as preLaunchTask in launch configurations for compilation before running.
Keyboard Shortcuts
| Action | Shortcut | Purpose |
|---|---|---|
| Navigation | ||
| Go to file | Ctrl + P | Quick file navigation with fuzzy search |
| Go to symbol | Ctrl + Shift + O | Navigate to symbols in current file |
| Go to definition | F12 | Jump to symbol definition |
| Peek definition | Alt + F12 | View definition inline (no navigation) |
| Find references | Shift + F12 | Find all references to symbol |
| Editing | ||
| Duplicate line | Alt + Shift + ↓ | Duplicate current line downward |
| Delete line | Ctrl + Shift + K | Delete current line |
| Move line up/down | Alt + ↑/↓ | Move line while maintaining indentation |
| Format document | Shift + Alt + F | Apply formatter (Prettier/ESLint) |
| Multi-Cursor | ||
| Add cursor above/below | Ctrl + Alt + ↑/↓ | Add cursors to adjacent lines |
| Select all occurrences | Ctrl + Shift + L | Add cursor to all occurrences of selection |
| Refactoring | ||
| Rename symbol | F2 | Rename symbol across project |
| Extract method | Ctrl + Shift + R | Extract selection to new function |
| Command Palette | ||
| Show commands | Ctrl + Shift + P | Access all VS Code commands |
| Terminal | ||
| Toggle terminal | Ctrl + ` | Show/hide integrated terminal |
| New terminal | Ctrl + Shift + `` `` | Create new terminal instance |
Multi-cursor editing (Ctrl + Shift + L) enables editing multiple identical occurrences simultaneously - invaluable for renaming variables across a function or updating multiple similar lines.
Local Development Environment
Environment parity minimizes differences between development and production, reducing "works on my machine" bugs. The Twelve-Factor App methodology defines parity across three dimensions: time (deploy quickly), personnel (developers deploy), and tools (same technologies everywhere).
Docker Compose for Local Dependencies
Docker Compose orchestrates multi-container applications, starting all service dependencies (databases, caches, message brokers) with consistent configuration. Running docker-compose up -d starts all dependencies in the background; docker-compose down -v destroys everything for a clean slate. See Docker best practices for optimization.
Compose Configuration
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:16-alpine
container_name: dev-postgres
environment:
POSTGRES_DB: payments
POSTGRES_USER: paymentuser
POSTGRES_PASSWORD: devpassword
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U paymentuser"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: dev-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes
kafka:
image: confluentinc/cp-kafka:latest
container_name: dev-kafka
depends_on:
- zookeeper
ports:
- "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
zookeeper:
image: confluentinc/cp-zookeeper:latest
container_name: dev-zookeeper
ports:
- "2181:2181"
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
volumes:
postgres_data:
redis_data:
Port mappings (5432:5432) expose container ports to the host, enabling applications to connect via localhost:5432. Volume mounts persist data between restarts - deleting containers doesn't lose database data. Health checks (pg_isready) ensure PostgreSQL fully initializes before dependent services start. See Spring Boot configuration for connecting to these services.
Database Initialization
The init-db.sql script runs automatically when PostgreSQL first starts via Docker's /docker-entrypoint-initdb.d/ directory, creating schemas and seed data:
-- scripts/init-db.sql
CREATE TABLE IF NOT EXISTS payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
amount DECIMAL(19, 4) NOT NULL,
currency VARCHAR(3) NOT NULL,
status VARCHAR(20) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_payments_user_id ON payments(user_id);
CREATE INDEX idx_payments_status ON payments(status);
-- Seed test data
INSERT INTO payments (user_id, amount, currency, status)
VALUES
('123e4567-e89b-12d3-a456-426614174000', 100.00, 'USD', 'COMPLETED'),
('123e4567-e89b-12d3-a456-426614174001', 250.50, 'EUR', 'PENDING');
Initialization scripts provide consistent starting state for all developers. Use Flyway migrations for production schema evolution, but initialization scripts bootstrap local development quickly.
Usage Commands
# Start all services (first time: pulls images, creates volumes)
docker-compose up -d
# View logs from all services (follow mode)
docker-compose logs -f
# View logs from specific service
docker-compose logs -f postgres
# Stop services (preserves volumes/data)
docker-compose stop
# Stop and remove containers (preserves volumes)
docker-compose down
# Stop, remove containers, and delete volumes (fresh start)
docker-compose down -v
# Rebuild containers after changing Dockerfiles
docker-compose up -d --build
For projects with environment-specific overrides: docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d.
Hot Reload Configuration
Hot reload automatically restarts/refreshes applications when source files change, reducing feedback time from minutes to seconds.
Spring Boot DevTools
Spring Boot DevTools uses two classloaders: one for dependencies (unchanged, not reloaded) and one for application code (reloaded). Only the application classloader restarts, making reloads 10x faster than full restarts.
// build.gradle
developmentOnly 'org.springframework.boot:spring-boot-devtools'
# application-dev.yml
spring:
devtools:
restart:
enabled: true
additional-paths: src/main/resources
livereload:
enabled: true
IntelliJ configuration: Enable "Build project automatically" (Settings → Build, Execution, Deployment → Compiler) and "Allow auto-make to start even if developed application is currently running" (Settings → Advanced Settings). This recompiles classes while the application runs, triggering DevTools restart.
React Fast Refresh
React Fast Refresh preserves component state while reloading changed components, maintaining form inputs, scroll position, and UI state. Enabled by default in Create React App and Vite. See React development for component patterns.
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3000,
host: true, // Expose to network for mobile device testing
hmr: {
overlay: true // Show error overlay in browser
}
}
});
Fast Refresh falls back to full page reload on syntax errors or changes to top-level exports - the browser console explains why.
Angular Hot Module Replacement
Angular CLI enables HMR with --hmr flag, reloading changed modules without full page refresh. Unlike React, Angular HMR doesn't preserve component state by default. See Angular development for change detection optimization.
# Start with HMR
ng serve --hmr
# Or configure in angular.json for automatic HMR
API Proxying
During development, frontend applications often call backend APIs running elsewhere. Proxy configuration redirects API requests from the frontend dev server to the backend, avoiding CORS issues:
Vite Proxy (React)
// vite.config.ts
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
});
Frontend code uses relative URLs (fetch('/api/payments')). The dev server intercepts /api requests and proxies to http://localhost:8080, rewriting /api/payments to /payments. changeOrigin modifies the Host header to match the target, required for some backends.
Angular Proxy
// proxy.conf.json
{
"/api": {
"target": "http://localhost:8080",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
}
}
# Start Angular with proxy
ng serve --proxy-config proxy.conf.json
Mock Services
Mock services simulate external dependencies when real services are unavailable, unstable, or expensive. Mocks provide predictable responses, support specific test scenarios, and eliminate network latency.
WireMock for HTTP APIs
WireMock creates HTTP mocks with request matchers and response templates. It supports stateful scenarios, fault injection (delays, errors), and request verification:
// WireMock in tests
@Test
void testPaymentServiceWithMock() {
WireMockServer wireMock = new WireMockServer(8089);
wireMock.start();
// Configure mock response
wireMock.stubFor(get(urlEqualTo("/api/exchange-rate/USD/EUR"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody("{\"rate\": 0.85}")
.withStatus(200)));
// Application calls http://localhost:8089/api/exchange-rate/USD/EUR
// and receives mocked response
}
WireMock is valuable when integrating third-party APIs (payment gateways, KYC providers) that charge per request or have rate limits. Develop against mocks locally, switch to real APIs in staging/production. Standalone WireMock runs via Docker:
# Run WireMock standalone
docker run -p 8080:8080 -v $PWD/mappings:/home/wiremock/mappings wiremock/wiremock
// mappings/exchange-rate.json
{
"request": {
"method": "GET",
"urlPattern": "/api/exchange-rate/([A-Z]{3})/([A-Z]{3})"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"rate": 0.85,
"timestamp": "{{now}}"
}
}
}
JSON Server for Quick Prototypes
JSON Server creates a full REST API from a JSON file, ideal for frontend development when the backend API isn't ready:
// db.json
{
"payments": [
{
"id": 1,
"userId": "user-123",
"amount": 100.00,
"currency": "USD",
"status": "COMPLETED"
}
]
}
# Install and run
npm install -g json-server
json-server --watch db.json --port 3001
# Available endpoints automatically generated:
# GET /payments
# GET /payments/1
# POST /payments
# PUT /payments/1
# DELETE /payments/1
# Filtering: GET /payments?status=COMPLETED
# Sorting: GET /payments?_sort=amount&_order=desc
JSON Server supports relationships, full-text search, and custom routes. Not suitable for production but excellent for rapid frontend prototyping.
Environment Parity Best Practices
Match Production Versions
Use identical database, cache, and runtime versions in development and production. Version mismatches cause subtle bugs - PostgreSQL 14 to 15 changed authentication methods and added SQL features. Code developed against 14 might fail on 15.
# docker-compose.yml - Pin exact versions (not :latest)
services:
postgres:
image: postgres:16.4-alpine # Exact version
redis:
image: redis:7.0.11-alpine # Exact version
java:
image: eclipse-temurin:21-jre-jammy # Matches production JRE
Avoid :latest tags - they make builds non-reproducible. :latest today differs from :latest next month. Pin versions and update deliberately after testing.
Configuration via Environment Variables
Use environment variables for configuration, never hardcode values. This allows identical application code to run everywhere with environment-specific configuration:
# application.yml (common across all environments)
spring:
datasource:
url: ${DATABASE_URL}
username: ${DATABASE_USERNAME}
password: ${DATABASE_PASSWORD}
kafka:
bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS}
# .env file for local development (not committed to Git)
DATABASE_URL=jdbc:postgresql://localhost:5432/payments
DATABASE_USERNAME=paymentuser
DATABASE_PASSWORD=devpassword
KAFKA_BOOTSTRAP_SERVERS=localhost:9092
Production environments inject configuration via Kubernetes ConfigMaps/Secrets, but application code remains identical. See Spring Boot configuration management and Secrets Management.
Anti-Patterns
IDE Configuration Anti-Patterns
Not Version Controlling Settings: Committing only code without IDE configurations forces each developer to manually configure their IDE, leading to inconsistent formatting and frequent whitespace conflicts.
Over-Reliance on Mouse: Using the mouse for actions with keyboard shortcuts slows development dramatically. Force yourself to use shortcuts - install Key Promoter X to learn them.
Plugin Bloat: Installing too many plugins degrades IDE performance. Only install plugins providing clear value; uninstall unused ones.
Ignoring Code Formatting Warnings: Disabling or ignoring formatting warnings leads to inconsistent code style. Fix formatting issues immediately; better yet, enable format-on-save.
Hardcoding Configuration: Hardcoding database URLs, API endpoints, or credentials in code requires changing code to run in different environments. Use environment variables and externalized configuration.
Using :latest Docker Tags: Using :latest tags makes builds non-reproducible and can introduce breaking changes unexpectedly. Always pin specific versions.
Skipping Hot Reload Setup: Manually stopping/restarting applications wastes time. Configure hot reload to reduce feedback loops from minutes to seconds.
Not Matching Production Versions: Running different database or runtime versions locally than production leads to "works on my machine" bugs. Match production versions exactly.
Manual Data Seeding: Manually creating test data through UI for each developer is time-consuming and error-prone. Automate with SQL scripts or factory libraries.
Ignoring Resource Limits: Running Docker containers without resource limits can consume all system resources, degrading performance. Set memory/CPU limits matching production constraints.
Examples
Complete IntelliJ Setup Workflow
# 1. Clone repository
git clone https://gitlab.com/bank/payment-service.git
cd payment-service
# 2. Start local dependencies
docker-compose up -d
# 3. Open project in IntelliJ IDEA
# File → Open → Select payment-service directory
# 4. Wait for Gradle sync and indexing
# 5. Install recommended plugins (Settings → Plugins)
# - Lombok, SonarLint, CheckStyle-IDEA, GitLab Integration
# 6. Code style automatically applied from .idea/codeStyles/Project.xml
# 7. Run configuration automatically available from .idea/runConfigurations/
# 8. Run application (Shift + F10)
# Application starts with dev profile, connects to Docker Compose databases
# 9. Run tests (Ctrl + Shift + F10 on test class)
Complete VS Code Setup Workflow
# 1. Clone repository
git clone https://gitlab.com/bank/payment-service.git
cd payment-service
# 2. Start local dependencies
docker-compose up -d
# 3. Open in VS Code
code .
# 4. Install recommended extensions (notification appears)
# Click "Install All" on extension recommendations
# 5. Settings automatically applied from .vscode/settings.json
# 6. Run configuration available in Run panel (Ctrl + Shift + D)
# 7. Start debugging (F5)
Local Development with Mock Services
# 1. Start dependencies and mocks
docker-compose -f docker-compose.yml -f docker-compose.mocks.yml up -d
# 2. Configure application to use mock endpoints
export EXCHANGE_RATE_API_URL=http://localhost:8089/api/exchange-rate
export FRAUD_SERVICE_URL=http://localhost:8089/api/fraud-check
# 3. Run application
./gradlew bootRun --no-daemon
# 4. Application calls mocks instead of real external services
# Develop without API rate limits or charges
Further Reading
Internal Documentation
- Git Branching Strategy - GitFlow workflow and branch management
- Code Review - Review process and requirements
- Repository Structure - Project organization patterns
- CI/CD Pipelines - Automation and deployment
- Docker - Container development workflow
- Spring Boot - Java backend development
- Database Migrations - Flyway migration patterns
- Secrets Management - Handling sensitive configuration
- Java Testing - Testing patterns and frameworks
External Resources
- IntelliJ IDEA Documentation - Official JetBrains documentation
- VS Code Java Documentation - Java extension pack guide
- EditorConfig Documentation - Cross-editor configuration
- Checkstyle Documentation - Code style checker
- Docker Compose Documentation - Multi-container orchestration
- WireMock Documentation - HTTP mocking
- Twelve-Factor App - Methodology for cloud-native applications
- Spring Boot DevTools Reference - Hot reload configuration
Related Guidelines
- Java Code Review Checklist - Code quality standards
- TypeScript Code Review Checklist - TypeScript quality standards
- Spring Boot Testing - Testing strategies
- React Testing - Frontend testing patterns
- Angular Testing - Angular testing approaches
- Pull Request Guidelines - PR sizing and templates