Use AI to Save Hours for Dev: Detailed Explanation with Examples

This article has a short video:TikTok

1. Overview

This article addresses a very practical question: how to use AI (specifically LLM like GPT, Claude) to save time in daily coding work without becoming a mindless “copy-paste” person. You’ll see how to integrate LLM into Spring Boot applications using Spring AI, how to use AI to generate tests, generate Javadoc, and what pitfalls to avoid.

After reading, you’ll be able to write a ChatClient that calls LLM in Spring Boot yourself, know how to write prompts for different tasks (generating code, reviewing, writing tests), and understand when AI actually helps and when it just slows you down.

2. Where AI Saves Time in Dev Work

AI isn’t faster than you everywhere. Three types of work where AI really pays off:

  • Repetitive work with clear patterns: generating DTOs from entities, generating getters/setters (even though IDE can do it), generating boilerplate test cases.
  • Work requiring quick information synthesis: reading long stack traces, summarizing PRs, explaining legacy code without comments.
  • Work needing a first draft: writing Javadoc, writing README, writing complex SQL queries for you to refine.

Conversely, AI doesn’t save time when: you don’t understand the problem clearly (vague prompt yields vague output), complex business logic specific to your company, or code requiring high accuracy in security/finance that you don’t review carefully.

Note: If AI generates code fast but you review it slowly, total time doesn’t decrease—sometimes it even increases because you have to debug code that “looks right but has wrong logic.”

3. Integrate LLM into Spring Boot with Spring AI

Instead of manually calling REST APIs with RestTemplate, use Spring AI to get a convenient ChatClient.

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

Declare the API key in application.properties:

spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.options.model=gpt-4o-mini

Create a service wrapping ChatClient:

@Service
public class CodeAssistantService {

    private final ChatClient chatClient;

    public CodeAssistantService(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    public String explainStackTrace(String stackTrace) {
        String prompt = """
                You are a senior Java engineer. Briefly explain the root cause
                of the following stack trace and suggest a fix:
                %s
                """.formatted(stackTrace);

        return chatClient.prompt()
                .user(prompt)
                .call()
                .content();
    }
}
  • ChatClient.Builder is auto-configured by Spring Boot thanks to the starter above; you just inject it.
  • prompt().user(...).call().content() sends the prompt and returns the response string without needing manual JSON parsing.
  • The explainStackTrace method takes a long stack trace and returns a brief explanation — this saves 5–10 minutes reading logs each time you debug an unusual error.

Try it in a controller:

@RestController
@RequestMapping("/api/assist")
public class CodeAssistantController {

    private final CodeAssistantService assistantService;

    public CodeAssistantController(CodeAssistantService assistantService) {
        this.assistantService = assistantService;
    }

    @PostMapping("/explain")
    public String explain(@RequestBody String stackTrace) {
        return assistantService.explainStackTrace(stackTrace);
    }
}

Calling POST /api/assist/explain with a NullPointerException stack trace as the body returns an explanation:

The NullPointerException occurs because the `customer` variable was not
initialized before calling `customer.getEmail()`. Check the OrderService —
the repository might be returning null when no record is found. You should
use Optional or check for null before accessing properties.

4. Use AI to Auto-generate Unit Tests — Concrete Example

One of the most time-consuming things is writing test cases for each logic branch. Suppose you have a DiscountCalculator class:

public class DiscountCalculator {

    public BigDecimal calculate(BigDecimal orderAmount, CustomerTier tier) {
        if (orderAmount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Order amount must be positive");
        }
        BigDecimal rate = switch (tier) {
            case GOLD -> new BigDecimal("0.15");
            case SILVER -> new BigDecimal("0.10");
            case BASIC -> new BigDecimal("0.05");
        };
        return orderAmount.multiply(rate).setScale(2, RoundingMode.HALF_UP);
    }
}

A sample prompt to ask LLM to generate tests (copy the class into the prompt):

Write JUnit 5 + AssertJ unit tests for the DiscountCalculator class.
Include cases for: GOLD, SILVER, BASIC, and cases where orderAmount <= 0 must throw
IllegalArgumentException. Use @ParameterizedTest where appropriate.

AI typically returns a draft close to correct; you adjust it to match your team’s style:

class DiscountCalculatorTest {

    private final DiscountCalculator calculator = new DiscountCalculator();

    @ParameterizedTest
    @CsvSource({
            "GOLD, 100, 15.00",
            "SILVER, 100, 10.00",
            "BASIC, 100, 5.00"
    })
    void shouldCalculateDiscountByTier(CustomerTier tier, String amount, String expected) {
        BigDecimal result = calculator.calculate(new BigDecimal(amount), tier);

        assertThat(result).isEqualByComparingTo(new BigDecimal(expected));
    }

    @Test
    void shouldThrowWhenOrderAmountIsNotPositive() {
        assertThatThrownBy(() -> calculator.calculate(BigDecimal.ZERO, CustomerTier.GOLD))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessage("Order amount must be positive");
    }
}
  • @ParameterizedTest + @CsvSource combines 3 tier cases into one test, reducing duplicate test code.
  • The line assertThat(result).isEqualByComparingTo(...) compares BigDecimal correctly (avoiding equals scale issues).
  • The remaining case checks the exception and message specifically.

Where AI saves time here is enumerating all edge cases (you’re likely to forget the orderAmount <= 0 case), while what you must do is verify that the expected values are correct — AI miscalculating numbers is common.

5. Use AI to Generate Javadoc and Summarize Legacy Code

For old code without comments, AI summarizes faster than reading line by line. For example, you have complex handling:

public List<Order> filterEligibleOrders(List<Order> orders) {
    return orders.stream()
            .filter(o -> o.getStatus() == OrderStatus.PAID)
            .filter(o -> o.getCreatedAt().isAfter(LocalDate.now().minusDays(30)))
            .filter(o -> o.getItems().stream().anyMatch(Item::isRefundable))
            .toList();
}

Prompt: “Write Javadoc for this method, clearly stating the 3 filtering conditions.” AI returns:

/**
 * Filters the list of orders eligible for refund processing.
 * Conditions: order must be in PAID status, created within the last 30 days,
 * and have at least one item eligible for refund.
 *
 * @param orders input list of orders
 * @return list of orders satisfying all three conditions above
 */

This saves onboarding time for new people joining an old codebase — paste a batch of methods, AI generates draft Javadoc, you only fix domain terminology errors.

6. Automate Code Review with AI in CI

You can add an AI review step in your pipeline to catch bugs early, before human review. Example simple step in GitHub Actions calling an API:

name: ai-review
on: [pull_request]

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Get diff
        run: git diff origin/main...HEAD > diff.txt
      - name: Call AI review
        run: |
          curl -s https://api.openai.com/v1/chat/completions \
            -H "Authorization: Bearer ${{ secrets.OPENAI_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d "$(jq -n --rawfile diff diff.txt \
              '{model:"gpt-4o-mini", messages:[{role:"user", content: ("Review the following Java diff and only mention critical issues: " + $diff)}]}')" \
            > review.json
  • git diff origin/main...HEAD captures only the changed code in the PR, avoiding sending the whole repo (wasting tokens, slow).
  • jq -n --rawfile safely constructs the JSON body, avoiding special character escaping issues in code.
  • Result review.json you can post as a comment on the PR using another step.

Note: AI review doesn’t replace senior review. It only catches obvious errors (missing null checks, empty catch Exception, basic SQL injection). Don’t merge PRs just because AI “approved.”

7. Common Mistakes and When Not to Use AI

A few real pitfalls when using LLM in Java projects:

  • Hallucination of non-existent APIs: AI might suggest calling Order.markAsRefundable() when the actual class has no such method. Always compile first before trusting.
  • Correct syntax but wrong version: AI often mixes Java 8 style (new ArrayList<>()) with Java 21 (List.of(...)) inconsistently — you need to standardize according to your team’s coding convention.
  • Leaking sensitive information: don’t paste config with secrets, connection strings, or real customer data into prompts sent to third-party AI services.
  • Vague prompt → vague output: “write tests for this class” produces much weaker results than a prompt that specifies edge cases to cover.
  • Don’t use AI for: interest calculations, tax calculations, or any compliance-related logic where a small error is a legal issue — AI should only help scaffold the structure; the core calculation must be checked carefully by someone with domain expertise using real test data, not AI-imagined test cases.

A small checklist you should apply every time AI generates code:

1. Does the code compile?
2. Do AI's test cases actually cover real edge cases, or just "plausible-sounding" cases?
3. Did any sensitive information leak into the prompt?
4. Does the code style match your team's current conventions?

8. Conclusion

AI helps you save time most clearly on repetitive work and tasks needing quick drafts: generating test cases, explaining stack traces, writing Javadoc, preliminary diff review. Spring AI makes integrating LLM into Java applications simple—just a few lines of configuration and ChatClient.

The key point is you still have to review all output carefully — AI saves writing time, not understanding and verification time. Use it in the right place, with the right level of trust, and you’ll cut many repeated hours each week without sacrificing code quality.

llmspring-aiaiproductivityjava