Home About Me

A Practical Java Developer’s Toolchain for Faster Coding, Debugging, Testing, and Delivery

Java work is rarely slowed down by one single problem. More often, time disappears into repeated boilerplate, dependency conflicts, hard-to-reproduce production bugs, inconsistent database schemas, manual API testing, and deployment scripts that only work on one machine. A well-chosen toolchain does not replace engineering judgment, but it can remove a large amount of mechanical work and make defects visible much earlier.

The following guide walks through 17 tools across the Java development lifecycle: IDE enhancement, runtime diagnosis, performance profiling, code quality, API development, database work, build and deployment, and technical knowledge management.

IDE tools: turning the editor into the main productivity hub

For many Java developers, IntelliJ IDEA is where most of the day is spent. The Ultimate edition already has strong support for Spring Boot, microservices, Docker, refactoring, and intelligent completion. The right plugins make it more effective in the places where Java projects typically become repetitive or error-prone.

1. IntelliJ IDEA Ultimate with essential plugins

A practical plugin setup usually starts with these six:

<table> <thead> <tr> <th>Plugin</th> <th>What it does</th> <th>Where it helps</th> </tr> </thead> <tbody> <tr> <td>Key Promoter X</td> <td>Detects mouse-driven IDE actions and suggests shortcuts</td> <td>Helps new users build shortcut habits and experienced users reduce repetitive clicks</td> </tr> <tr> <td>AiXcoder Code Completer</td> <td>Provides AI-based contextual completion, method generation, parameter filling, and exception templates</td> <td>Useful when writing business logic, calling third-party APIs, or defining DTOs</td> </tr> <tr> <td>Maven Helper</td> <td>Visualizes Maven dependency trees and highlights version conflicts</td> <td>Quickly resolves jar conflicts that cause errors such as ClassNotFoundException</td> </tr> <tr> <td>Lombok</td> <td>Generates boilerplate through annotations such as getter/setter, toString, and builder</td> <td>Reduces repetitive code in DTO, VO, and Entity classes</td> </tr> <tr> <td>Rainbow Brackets</td> <td>Colors nested brackets differently in Java, XML, JSON, and related formats</td> <td>Makes deeply nested logic, lambdas, and chained calls easier to read</td> </tr> <tr> <td>SonarLint</td> <td>Detects vulnerabilities, code smells, and style problems while coding</td> <td>Prevents many review-time fixes before code is even committed</td> </tr> </tbody> </table>

A second layer of productivity comes from IntelliJ IDEA Live Templates. Common snippets such as logger declarations, exception handling blocks, and singleton implementations can be triggered by short abbreviations.

Logger template:

private static final Logger log = LoggerFactory.getLogger($CLASS_NAME$.class);

Exception handling template:

  try {
    $SELECTION$
  } catch (Exception e) {
    log.error("[$METHOD_NAME$] 执行失败,参数:{}", $PARAMS$, e);
    throw new BusinessException(ResultCode.FAIL, "操作失败");
  }

Enum singleton template:

  public enum $CLASS_NAME$ {
    INSTANCE;

    public void doSomething() {
        $BODY$
    }
  }

2. Lombok beyond boilerplate reduction

Lombok is often discussed as an IDE plugin, but it is actually a library that modifies generated bytecode through annotations. It should be added to pom.xml or build.gradle, and developers should understand its edge cases rather than only using @Data everywhere.

A common issue appears when @Builder is used together with frameworks that require a no-argument constructor, such as Jackson in Spring Boot JSON serialization or ORM frameworks such as MyBatis. The safe combination is:

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder // 生成Builder模式(支持链式调用:UserDTO.builder().id(1L).username("test").build())
@NoArgsConstructor // 显式生成无参构造
@AllArgsConstructor // 生成全参构造(配合Builder使用)
public class UserDTO {
    private Long id;
    private String username;
    private String email;
}

For objects with bidirectional references, equals and hashCode can recurse indefinitely. Excluding the circular field avoids stack overflow:

@Data
@EqualsAndHashCode(exclude = "orders") // 排除可能导致循环引用的orders字段
public class UserDTO {
    private Long id;
    private String username;
    private List<OrderDTO> orders; // 与OrderDTO存在循环引用
}

Sensitive fields should also be excluded from toString output:

@Data
@ToString(exclude = "password") // 隐藏password字段
public class LoginUserDTO {
    private String username;
    private String password; // 敏感字段,不参与toString
    private String email;
}

Debugging and profiling: finding problems where they actually happen

Production Java systems often fail in ways that cannot be reproduced locally: empty responses, memory leaks, slow APIs, thread blocking, or behavior that differs from the local build. The following tools are especially useful when normal debugging is not enough.

3. Arthas for live diagnosis

Arthas is an open-source Java diagnostic tool that can inspect a running application without restarting it. It supports JDK 6+ and is especially valuable when attaching a debugger in production is not possible.

Key commands include:

<table> <thead> <tr> <th>Command</th> <th>Purpose</th> <th>Example use</th> </tr> </thead> <tbody> <tr> <td>watch</td> <td>Observe method parameters, return values, and exceptions</td> <td>Check why an API returns null</td> </tr> <tr> <td>trace</td> <td>Trace a method call chain and measure child-method latency</td> <td>Find the slow part of an interface call</td> </tr> <tr> <td>jad</td> <td>Decompile a loaded class</td> <td>Confirm whether the deployed code is really the expected version</td> </tr> <tr> <td>sc</td> <td>Search loaded classes and inspect class loaders</td> <td>Diagnose class loading conflicts</td> </tr> <tr> <td>redefine</td> <td>Hot-update a class without restarting the JVM</td> <td>Apply a small emergency fix online</td> </tr> </tbody> </table>

Typical commands:

watch com.example.service.UserService queryUser "{params, returnObj, throwExp}" -x 3 -n 5
trace com.example.service.OrderService createOrder "#cost>50"
jad com.example.controller.UserController
sc -d com.example.service.UserService
redefine /tmp/UserService.class

For example, if a user query API returns empty data, start by watching queryUser to inspect the incoming user ID and returned object. If the return value is null, use trace to see whether the repository method is called. If the repository returns nothing, decompile the loaded repository class with jad to verify whether the SQL condition is correct. After fixing a small mistake locally, compile the class and apply it with redefine when appropriate.

4. JProfiler for CPU, memory, thread, and database analysis

JProfiler provides a visual way to analyze Java performance. It is suitable for offline performance testing and post-incident review, and it covers memory, CPU, threads, and database calls.

For memory leaks, take two heap snapshots: one after application startup and one after a heavy operation such as batch import. If objects such as List<User> keep increasing and remain referenced, the reference chain can reveal the owner, for example a static cache that was never cleared.

For CPU problems, sort methods by total execution time and average time. If OrderService.calculatePrice dominates runtime and most of the cost comes from repeated BigDecimal calculations in a loop, the code may be optimized by precomputing or caching intermediate results.

For threads, JProfiler can show states such as RUNNABLE, BLOCKED, and WAITING, and it can detect deadlocks. A typical deadlock is Thread-A holding lock1 while waiting for lock2, while Thread-B holds lock2 and waits for lock1. The call stack usually points directly to inconsistent lock acquisition order.

JProfiler can connect to online applications through JMX, but remote profiling has overhead. Use it during low-traffic periods, enable only the required views, and prefer lightweight heap snapshots when possible.

5. Charles and Fiddler for HTTP and HTTPS traffic debugging

Charles is commonly preferred on macOS, while Fiddler is widely used on Windows. Both can capture, modify, replay, and breakpoint HTTP/HTTPS requests. They are useful for frontend-backend debugging, third-party API integration, WebSocket investigation, weak-network simulation, and error-response testing.

A practical use case is verifying frontend behavior when the backend returns a 500 response. Instead of changing backend code, enable a breakpoint for /api/order/create, trigger the frontend request, edit the response status to 500, replace the body with an error payload such as "code":500,"msg":"服务器内部错误", and then continue execution.

Code quality: making defects visible before review

Working code is not always maintainable code. Java teams need guardrails for readability, security, testability, layering, and long-term architectural consistency.

6. SonarQube and SonarLint

SonarLint catches problems inside the IDE, while SonarQube provides project-level quality management. Together they can detect vulnerabilities such as SQL injection, bugs such as null pointer risks, code smells such as long methods or duplicated logic, insufficient comments, high complexity, duplicate code, and weak test coverage.

A common workflow is:

  1. Use SonarLint locally during coding.
  2. Run mvn sonar:sonar before committing when a SonarQube server is configured.
  3. Add a SonarQube scan to Jenkins or GitHub Actions.
  4. Fail the build when the quality gate is not satisfied, for example when coverage is below 80% or high-risk vulnerabilities exist.
  5. Review quality trends regularly and train the team on recurring issues such as unclosed streams.

Quality gates should match the project. For example, a team may block merges when high-risk vulnerabilities exist, line coverage is below 80%, or duplicated code exceeds 5%. Some rules can be excluded for specific packages such as utility classes.

7. ArchUnit for architectural rules

As projects grow, architecture often decays gradually: service classes depend on controllers, repositories contain business logic, or packages form circular dependencies. ArchUnit turns architectural constraints into executable tests.

For a typical Controller → Service → Repository structure, rules can prohibit Service from depending on Controller, prohibit Repository from depending on Service, and detect cycles inside controller packages:

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.Test;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*;
import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices;

public class ArchitectureTest {
    // 导入项目中所有需要检查的类(从target/classes目录)
    JavaClasses importedClasses = new ClassFileImporter()
            .importPackages("com.example.controller", "com.example.service", "com.example.repository");

    // 规则1:Service层不依赖Controller层
    @Test
    public void serviceShouldNotDependOnController() {
        ArchRule rule = noClasses()
                .that().resideInAPackage("com.example.service..") // ..表示包含子包
                .should().dependOnClassesThat().resideInAPackage("com.example.controller..");
        rule.check(importedClasses); // 违反规则则抛出异常,测试失败
    }

    // 规则2:Repository层不依赖Service层
    @Test
    public void repositoryShouldNotDependOnService() {
        ArchRule rule = noClasses()
                .that().resideInAPackage("com.example.repository..")
                .should().dependOnClassesThat().resideInAPackage("com.example.service..");
        rule.check(importedClasses);
    }

    // 规则3:禁止Controller层之间的循环依赖
    @Test
    public void controllerShouldNotHaveCycleDependencies() {
        ArchRule rule = slices().matching("com.example.controller.(*)..")
                .should().beFreeOfCycles(); // 检查同一个Controller子包内是否有循环依赖
        rule.check(importedClasses);
    }
}

Once these tests are part of mvn test, CI will reject architectural violations automatically.

8. JaCoCo for coverage measurement

JaCoCo measures line coverage, branch coverage, and method coverage. Line coverage shows whether code ran at least once; branch coverage is more important for if-else and switch logic; method coverage reveals methods that were not tested at all.

A Maven setup can generate reports and fail the build when thresholds are not met:

<build>
    <plugins>
        <!-- JaCoCo插件 -->
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <version>0.8.11</version>
            <executions>
                <!-- 1. 准备测试环境,记录代码执行轨迹 -->
                <execution>
                    <id>prepare-agent</id>
                    <goals>
                        <goal>prepare-agent</goal>
                    </goals>
                </execution>
                <!-- 2. 执行测试后生成覆盖率报告 -->
                <execution>
                    <id>report</id>
                    <phase>test</phase>
                    <goals>
                        <goal>report</goal>
                    </goals>
                    <configuration>
                        <!-- 报告输出目录 -->
                        <outputDirectory>${project.build.directory}/jacoco-report</outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

After mvn test, open target/jacoco-report/index.html. Red lines are uncovered, yellow branches are partially covered, and green code has been exercised. Add tests for exception paths and missed branches rather than chasing coverage numbers mechanically.

API development and collaboration

In frontend-backend separated projects, API drift is a frequent source of wasted time. Good tooling keeps documentation, mocks, tests, and generated code aligned.

9. Postman and Newman

Postman supports REST, GraphQL, HTTP/HTTPS requests, environments, collections, scripts, and mock servers. Define variables such as {baseUrl} for development, test, and production environments so URLs do not need to be edited by hand.

Pre-request scripts can generate dynamic values such as timestamps. Test scripts can validate status codes and response bodies:

    // 测试响应状态码为200
    pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
    });
    // 测试响应体包含"code":200
    pm.test("Response has code 200", function () {
    var jsonData = pm.response.json();
    pm.expect(jsonData.code).to.eql(200);
    });

Newman runs Postman collections from the command line and fits naturally into CI:

   newman run order-api.postman_collection.json -e test-environment.postman_environment.json -r html,junit --reporter-html-export newman-report.html

If an API assertion fails, Newman exits with a non-zero status, causing the CI build to fail.

10. OpenAPI Generator

OpenAPI defines APIs in YAML or JSON, including request parameters, response formats, and data models. OpenAPI Generator can produce clients in languages such as Java and JavaScript, and server stubs such as Spring Boot controllers and DTOs.

The strongest workflow is API-first: frontend and backend teams review the OpenAPI contract first, then develop in parallel. Generated code reduces manual controller and DTO boilerplate and helps prevent documentation from drifting away from implementation.

Example generation command:

   # 安装OpenAPI Generator(若未安装)
   npm install -g @openapitools/openapi-generator-cli
   # 生成Spring Boot服务器端代码
   openapi-generator generate -i swagger.yaml -g spring -o ./order-server \
    --additional-properties=library=spring-boot,java8=true,dateLibrary=java8

The generated project typically contains controller classes, request and response models, and service interfaces where business logic still needs to be implemented manually.

Database tools

Database work in Java projects spans SQL writing, schema understanding, migration control, environment synchronization, and troubleshooting slow queries.

11. DBeaver

DBeaver is an open-source database client that supports MySQL, PostgreSQL, Oracle, MongoDB, Redis, and many other databases. It can generate ER diagrams, provide SQL highlighting and completion, format SQL with Ctrl+Shift+F, analyze execution plans, and export or import data in formats such as Excel, CSV, JSON, and SQL scripts.

It is also useful for comparing two databases, such as development and test, then generating synchronization SQL to keep schemas aligned.

Common shortcuts include:

  • Ctrl+Enter: execute the current SQL statement.
  • Ctrl+/: comment or uncomment selected lines.
  • Alt+Shift+Down: duplicate the current line.
  • Ctrl+Space: trigger completion.

12. Flyway and Liquibase

Manual SQL execution is one of the easiest ways to make environments inconsistent. Flyway and Liquibase bring database changes under version control.

Flyway uses versioned SQL scripts such as V1__create_user_table.sql and V2__add_user_email_column.sql. A Spring Boot application checks flyway_schema_history at startup, runs unapplied scripts in order, and validates migrations when configured.

Example migration:

CREATE TABLE IF NOT EXISTS user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL COMMENT '用户名',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    UNIQUE KEY uk_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

Flyway is simple and SQL-centered. Liquibase supports SQL, XML, YAML, and JSON, describes changes through changeSet, and has stronger rollback support. Smaller teams that mainly write SQL often prefer Flyway; larger teams with multiple database types may benefit from Liquibase.

Build and deployment

A reliable delivery pipeline should build, test, package, containerize, and deploy with as little manual intervention as possible.

13. Gradle with Kotlin DSL

Maven remains common, but Gradle has clear advantages in incremental builds, dependency caching, multi-project builds, and flexible custom tasks. Kotlin DSL adds type safety and IDE completion compared with Groovy DSL.

Common commands:

  • ./gradlew build: compile, test, and package.
  • ./gradlew bootRun: run a Spring Boot app locally.
  • ./gradlew clean: remove the build directory.
  • ./gradlew dependencies: inspect the dependency tree.
  • ./gradlew test: run only tests.

14. Docker and Docker Compose

Docker packages the application together with runtime dependencies, solving the classic “works on my machine” problem. Docker Compose defines application, MySQL, Redis, and other services in one YAML file for one-command startup.

A common Java Dockerfile uses a multi-stage build: the first stage compiles the Spring Boot JAR with Gradle or Maven, while the second runs it on a lightweight JRE image. Compose can then start the application alongside MySQL and Redis, mount volumes for persistence, and define environment variables for Spring datasource and Redis configuration.

Useful commands:

  • docker-compose up -d: start all services in the background.
  • docker-compose logs -f app: follow application logs.
  • docker-compose exec mysql bash: enter the MySQL container.
  • docker-compose down: stop and remove services while keeping volumes.
  • docker-compose down -v: remove services and volumes; use carefully because data is deleted.

15. GitHub Actions and Jenkins

GitHub Actions is lightweight and convenient for open-source projects and smaller teams. Workflows live under /.github/workflows/ and can run on pushes, pull requests, or other events. A typical Java workflow checks out code, sets up JDK 17, caches Gradle dependencies, runs ./gradlew build, uploads JaCoCo reports, and builds a Docker image after successful tests.

Jenkins is better suited for enterprise pipelines with complex environments, private registries, SonarQube integration, Docker, Kubernetes, manual approvals, and multi-stage deployments. A Jenkins Pipeline can include checkout, SonarQube analysis, build and test, Docker image creation, push to a private registry, manual confirmation, deployment through SSH, and success or failure notifications.

Documentation and knowledge management

Long-term engineering efficiency also depends on how well teams record designs, incidents, and reusable knowledge.

16. PlantUML

PlantUML generates UML diagrams from text, making diagrams easier to version, review, and update than drag-and-drop tools. It supports class diagrams, sequence diagrams, use case diagrams, and many other diagram types.

Class diagram example:

  @startuml
  ' 设置类的样式
  skinparam classAttributeIconSize 0
  skinparam classStyle strictuml2

  ' 定义类
  class User {
  - id: Long
  - username: String
  - email: String
  + getUserById(id: Long): User
  + saveUser(user: User): void
  }

  class Order {
  - id: Long
  - orderNo: String
  - totalAmount: BigDecimal
  + createOrder(order: Order): Order
  + cancelOrder(id: Long): void
  }

  class Product {
  - id: Long
  - name: String
  - price: BigDecimal
  }

  ' 定义关系(1对多:User "*--1" Order 表示一个User有多个Order)
  User "1" *-- "n" Order : has
  Order "n" *-- "n" Product : contains
  @enduml

In IntelliJ IDEA, install the PlantUML plugin, install Graphviz, configure the dot executable path, and write .puml files with live preview. Diagrams can be exported as PNG, SVG, or PDF.

17. Obsidian and Logseq

Obsidian and Logseq are local Markdown-based knowledge management tools. Both support bidirectional links, tags, graph views, and code blocks, making them useful for technical notes, incident records, architecture documents, and learning logs.

Recommended note categories include:

  • Technical topics such as JVM, Spring, and MySQL.
  • Troubleshooting notes structured as symptoms, cause, solution, and prevention.
  • Project documentation covering architecture, technology choices, APIs, and core flows.
  • Daily learning notes for newly learned concepts.

Obsidian uses a familiar folder-and-file model and has a rich plugin ecosystem, which makes it comfortable for long-form documentation. Logseq uses block editing and an outline-first style, which works well for fragmented knowledge and quick capture.

Principles for an effective Java toolchain

Tools are most effective when introduced deliberately. Add one or two at a time, learn the core workflow deeply, and only expand after the team has absorbed them. Choose tools according to the job: JProfiler for memory analysis, Arthas for live diagnosis, Newman for automated API tests, and Flyway or Liquibase for schema consistency.

The highest return usually comes from automation. Put repetitive work such as API testing, code quality scanning, architecture checks, and coverage thresholds into CI. As the technology stack evolves, the toolchain should evolve with it, whether that means upgrading Gradle, moving to Java 17, or adapting to a newer Spring Boot baseline.

Real efficiency does not come from collecting tools. It comes from understanding where each tool fits, using it fluently, and combining it with sound engineering judgment.