LGTM.com Integration in Java: Complete Guide

Introduction to LGTM Integration

LGTM.com (Looks Good To Me) is a code analysis platform that helps identify security vulnerabilities, bugs, and code quality issues. This guide covers integrating LGTM into Java projects for continuous code analysis and quality monitoring.


System Architecture Overview

LGTM Integration Pipeline
├── Code Analysis Setup
│   ├ - LGTM Project Configuration
│   ├ - Analysis Configuration
│   ├ - Query Suites
│   └ - Custom Queries
├── CI/CD Integration
│   ├ - GitHub Actions
│   ├ - Jenkins Pipeline
│   ├ - GitLab CI
│   └ - Local Analysis
├── Results Processing
│   ├ - Alert Management
│   ├ - Result Filtering
│   ├ - Report Generation
│   └ - Trend Analysis
└── Developer Workflow
├ - IDE Integration
├ - PR Checks
├ - Notifications
â”” - Fix Verification

Core Implementation

1. Maven Dependencies

<properties>
<lgtm.maven.plugin.version>1.0</lgtm.maven.plugin.version>
<github.api.version>1.1.0</github.api.version>
<jackson.version>2.15.2</jackson.version>
<okhttp.version>4.11.0</okhttp.version>
</properties>
<dependencies>
<!-- LGTM API Client -->
<dependency>
<groupId>com.lgtm</groupId>
<artifactId>lgtm-java-client</artifactId>
<version>1.0.0</version>
</dependency>
<!-- GitHub API for PR integration -->
<dependency>
<groupId>org.kohsuke</groupId>
<artifactId>github-api</artifactId>
<version>${github.api.version}</version>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Maven Plugin API -->
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.8.1</version>
</dependency>
<!-- Code Analysis Tools -->
<dependency>
<groupId>org.sonarsource.sonarlint.core</groupId>
<artifactId>sonarlint-core</artifactId>
<version>8.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- LGTM Maven Plugin -->
<plugin>
<groupId>com.lgtm</groupId>
<artifactId>lgtm-maven-plugin</artifactId>
<version>${lgtm.maven.plugin.version}</version>
<executions>
<execution>
<goals>
<goal>analyze</goal>
</goals>
</execution>
</executions>
<configuration>
<outputDirectory>${project.build.directory}/lgtm</outputDirectory>
<format>sarif</format>
<failOnCritical>true</failOnCritical>
</configuration>
</plugin>
</plugins>
</build>

2. LGTM Configuration Files

lgtm.yml

# lgtm.yml - LGTM analysis configuration
path_classifiers:
docs:
- "**/*.md"
- "**/*.rst"
- "**/*.txt"
code:
- "**/*.java"
- "**/*.kt"  # Kotlin files
test:
- "**/test/**"
- "**/*Test.java"
- "**/*Spec.java"
extraction:
java:
index:
# Java version for analysis
jdk: 11
# Build configuration
maven:
# Maven goals to run for building the project
goals:
- "clean"
- "compile"
- "test-compile"
# Exclude generated code and dependencies
exclude:
- "**/target/**"
- "**/generated/**"
- "**/node_modules/**"
queries:
# Include security queries
- include: java/security
# Include performance queries
- include: java/performance
# Include maintainability queries
- include: java/maintainability
# Exclude specific queries
- exclude: java/security/audit/xss
# Custom query packs
- pack: custom-queries
query_filters:
- exclude: "**/test/**"
- exclude: "**/*Test.java"
alerters:
# PR integration settings
pull_requests:
comments: true
status: true
# Only show new alerts in PRs
new_alerts_only: true
# Alert threshold
threshold: warning

Custom Query Suite

# custom-queries.yml
name: "custom-java-queries"
version: "1.0.0"
description: "Custom Java queries for our project"
queries:
- id: "custom/sql-injection"
name: "Potential SQL Injection"
description: "Detects potential SQL injection vulnerabilities"
severity: error
tags: [security, sql]
query: |
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
from MethodAccess call, Expr source
where
call.getMethod().hasName("executeQuery") and
TaintTracking::localTaint(DataFlow::parameterNode(source), DataFlow::exprNode(call.getArgument(0)))
select call, "Potential SQL injection from $@", source, source.toString()
- id: "custom/log-injection"
name: "Log Injection"
description: "Detects user input in log statements"
severity: warning
tags: [security, logging]
query: |
import java
import semmle.code.java.dataflow.DataFlow
from MethodAccess logCall, Expr userInput
where
logCall.getMethod().getName().matches("info|warn|error|debug") and
logCall.getMethod().getDeclaringType().getASupertype*().hasQualifiedName("org.slf4j", "Logger") and
DataFlow::localFlow(DataFlow::parameterNode(userInput), DataFlow::exprNode(logCall.getArgument(0)))
select logCall, "User input used in log statement: $@", userInput, userInput.toString()

3. LGTM API Client

package com.example.lgtm.client;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Component
public class LGTMApiClient {
private static final String BASE_URL = "https://lgtm.com/api/v1.0";
private final OkHttpClient client;
private final ObjectMapper objectMapper;
@Value("${lgtm.api.token:}")
private String apiToken;
public LGTMApiClient() {
this.client = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build();
this.objectMapper = new ObjectMapper();
}
public AnalysisResult getProjectAnalysis(String projectId) throws IOException {
Request request = new Request.Builder()
.url(BASE_URL + "/projects/" + projectId + "/analysis")
.addHeader("Authorization", "Bearer " + apiToken)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code: " + response);
}
return objectMapper.readValue(response.body().string(), AnalysisResult.class);
}
}
public List<Alert> getProjectAlerts(String projectId, String severity) throws IOException {
HttpUrl.Builder urlBuilder = HttpUrl.parse(BASE_URL + "/projects/" + projectId + "/alerts").newBuilder();
if (severity != null) {
urlBuilder.addQueryParameter("severity", severity);
}
Request request = new Request.Builder()
.url(urlBuilder.build())
.addHeader("Authorization", "Bearer " + apiToken)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code: " + response);
}
return objectMapper.readValue(
response.body().string(),
objectMapper.getTypeFactory().constructCollectionType(List.class, Alert.class)
);
}
}
public void triggerAnalysis(String projectId) throws IOException {
Request request = new Request.Builder()
.url(BASE_URL + "/projects/" + projectId + "/analyses")
.post(RequestBody.create("", MediaType.get("application/json")))
.addHeader("Authorization", "Bearer " + apiToken)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Failed to trigger analysis: " + response);
}
}
}
public AnalysisStatus getAnalysisStatus(String analysisId) throws IOException {
Request request = new Request.Builder()
.url(BASE_URL + "/analyses/" + analysisId)
.addHeader("Authorization", "Bearer " + apiToken)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code: " + response);
}
return objectMapper.readValue(response.body().string(), AnalysisStatus.class);
}
}
// Data models
public static class AnalysisResult {
private String id;
private String status;
private String projectId;
private int alertCount;
private String commitId;
private String createdAt;
private String completedAt;
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getProjectId() { return projectId; }
public void setProjectId(String projectId) { this.projectId = projectId; }
public int getAlertCount() { return alertCount; }
public void setAlertCount(int alertCount) { this.alertCount = alertCount; }
public String getCommitId() { return commitId; }
public void setCommitId(String commitId) { this.commitId = commitId; }
public String getCreatedAt() { return createdAt; }
public void setCreatedAt(String createdAt) { this.createdAt = createdAt; }
public String getCompletedAt() { return completedAt; }
public void setCompletedAt(String completedAt) { this.completedAt = completedAt; }
}
public static class Alert {
private String id;
private String queryId;
private String file;
private int line;
private String severity;
private String message;
private String description;
private String category;
private boolean fixed;
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getQueryId() { return queryId; }
public void setQueryId(String queryId) { this.queryId = queryId; }
public String getFile() { return file; }
public void setFile(String file) { this.file = file; }
public int getLine() { return line; }
public void setLine(int line) { this.line = line; }
public String getSeverity() { return severity; }
public void setSeverity(String severity) { this.severity = severity; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getCategory() { return category; }
public void setCategory(String category) { this.category = category; }
public boolean isFixed() { return fixed; }
public void setFixed(boolean fixed) { this.fixed = fixed; }
}
public static class AnalysisStatus {
private String id;
private String status;
private String projectId;
private int progress;
private String startedAt;
private String finishedAt;
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getProjectId() { return projectId; }
public void setProjectId(String projectId) { this.projectId = projectId; }
public int getProgress() { return progress; }
public void setProgress(int progress) { this.progress = progress; }
public String getStartedAt() { return startedAt; }
public void setStartedAt(String startedAt) { this.startedAt = startedAt; }
public String getFinishedAt() { return finishedAt; }
public void setFinishedAt(String finishedAt) { this.finishedAt = finishedAt; }
}
}

4. LGTM Analysis Service

package com.example.lgtm.service;
import com.example.lgtm.client.LGTMApiClient;
import com.example.lgtm.client.LGTMApiClient.Alert;
import com.example.lgtm.client.LGTMApiClient.AnalysisResult;
import com.example.lgtm.client.LGTMApiClient.AnalysisStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Service
public class LGTMAnalysisService {
private static final Logger logger = LoggerFactory.getLogger(LGTMAnalysisService.class);
private final LGTMApiClient lgtmClient;
private final GitHubService githubService;
private final NotificationService notificationService;
@Value("${lgtm.project.id:}")
private String projectId;
@Value("${lgtm.analysis.timeout:600}")
private int analysisTimeoutSeconds;
public LGTMAnalysisService(LGTMApiClient lgtmClient, 
GitHubService githubService,
NotificationService notificationService) {
this.lgtmClient = lgtmClient;
this.githubService = githubService;
this.notificationService = notificationService;
}
/**
* Trigger LGTM analysis and wait for completion
*/
public AnalysisResult triggerAndWaitForAnalysis() throws Exception {
logger.info("Triggering LGTM analysis for project: {}", projectId);
// Trigger analysis
lgtmClient.triggerAnalysis(projectId);
// Wait for analysis to complete
AnalysisResult result = waitForAnalysisCompletion();
logger.info("LGTM analysis completed with {} alerts", result.getAlertCount());
return result;
}
/**
* Get current analysis results
*/
public AnalysisResult getCurrentAnalysis() throws Exception {
return lgtmClient.getProjectAnalysis(projectId);
}
/**
* Get alerts filtered by severity
*/
public List<Alert> getAlertsBySeverity(String severity) throws Exception {
return lgtmClient.getProjectAlerts(projectId, severity);
}
/**
* Get alerts grouped by category
*/
public Map<String, List<Alert>> getAlertsByCategory() throws Exception {
List<Alert> allAlerts = lgtmClient.getProjectAlerts(projectId, null);
return allAlerts.stream()
.collect(Collectors.groupingBy(Alert::getCategory));
}
/**
* Get alert statistics
*/
public AlertStatistics getAlertStatistics() throws Exception {
List<Alert> allAlerts = lgtmClient.getProjectAlerts(projectId, null);
AlertStatistics stats = new AlertStatistics();
stats.setTotalAlerts(allAlerts.size());
stats.setCriticalAlerts((int) allAlerts.stream().filter(a -> "error".equals(a.getSeverity())).count());
stats.setWarningAlerts((int) allAlerts.stream().filter(a -> "warning".equals(a.getSeverity())).count());
stats.setFixedAlerts((int) allAlerts.stream().filter(Alert::isFixed).count());
// Group by category
Map<String, Long> categoryCounts = allAlerts.stream()
.collect(Collectors.groupingBy(Alert::getCategory, Collectors.counting()));
stats.setAlertsByCategory(categoryCounts);
// Group by file
Map<String, Long> fileCounts = allAlerts.stream()
.collect(Collectors.groupingBy(Alert::getFile, Collectors.counting()));
stats.setAlertsByFile(fileCounts);
return stats;
}
/**
* Analyze pull request and post comments
*/
public PullRequestAnalysis analyzePullRequest(String repo, int pullRequestNumber) throws Exception {
logger.info("Analyzing pull request {}/{}", repo, pullRequestNumber);
PullRequestAnalysis prAnalysis = new PullRequestAnalysis();
prAnalysis.setRepository(repo);
prAnalysis.setPullRequestNumber(pullRequestNumber);
// Get PR details
PullRequestDetails prDetails = githubService.getPullRequestDetails(repo, pullRequestNumber);
prAnalysis.setBaseCommit(prDetails.getBaseCommit());
prAnalysis.setHeadCommit(prDetails.getHeadCommit());
// Trigger analysis for the PR
lgtmClient.triggerAnalysis(projectId);
AnalysisResult analysis = waitForAnalysisCompletion();
// Get new alerts (compared to base)
List<Alert> newAlerts = getNewAlerts(prDetails.getBaseCommit(), prDetails.getHeadCommit());
prAnalysis.setNewAlerts(newAlerts);
// Get fixed alerts
List<Alert> fixedAlerts = getFixedAlerts(prDetails.getBaseCommit(), prDetails.getHeadCommit());
prAnalysis.setFixedAlerts(fixedAlerts);
// Post PR comments
if (!newAlerts.isEmpty()) {
postPRComments(repo, pullRequestNumber, newAlerts);
}
// Update PR status
updatePRStatus(repo, pullRequestNumber, newAlerts.isEmpty());
// Send notifications
sendAnalysisNotifications(prAnalysis);
return prAnalysis;
}
/**
* Generate analysis report
*/
public AnalysisReport generateReport() throws Exception {
AnalysisResult currentAnalysis = getCurrentAnalysis();
AlertStatistics statistics = getAlertStatistics();
List<Alert> criticalAlerts = getAlertsBySeverity("error");
List<Alert> warningAlerts = getAlertsBySeverity("warning");
AnalysisReport report = new AnalysisReport();
report.setGeneratedAt(new Date());
report.setProjectId(projectId);
report.setAnalysisId(currentAnalysis.getId());
report.setTotalAlerts(currentAnalysis.getAlertCount());
report.setStatistics(statistics);
report.setCriticalAlerts(criticalAlerts);
report.setWarningAlerts(warningAlerts);
report.setTrend(getAlertTrend());
return report;
}
/**
* Export alerts to SARIF format
*/
public String exportToSarif() throws Exception {
List<Alert> allAlerts = lgtmClient.getProjectAlerts(projectId, null);
SarifReport sarif = new SarifReport();
sarif.setVersion("2.1.0");
List<SarifReport.Run> runs = new ArrayList<>();
SarifReport.Run run = new SarifReport.Run();
List<SarifReport.Result> results = allAlerts.stream()
.map(this::convertAlertToSarifResult)
.collect(Collectors.toList());
run.setResults(results);
runs.add(run);
sarif.setRuns(runs);
// Convert to JSON
ObjectMapper mapper = new ObjectMapper();
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(sarif);
}
/**
* Run local analysis using LGTM CLI
*/
public LocalAnalysisResult runLocalAnalysis(Path projectPath) throws Exception {
logger.info("Running local LGTM analysis on: {}", projectPath);
LocalAnalysisResult result = new LocalAnalysisResult();
result.setProjectPath(projectPath.toString());
result.setStartedAt(new Date());
// Check if LGTM CLI is available
if (!isLGTMCliAvailable()) {
throw new IllegalStateException("LGTM CLI is not available");
}
// Run analysis
ProcessBuilder pb = new ProcessBuilder("lgtm", "analyze", "--format", "sarif", projectPath.toString());
pb.directory(projectPath.toFile());
Process process = pb.start();
boolean completed = process.waitFor(analysisTimeoutSeconds, TimeUnit.SECONDS);
if (!completed) {
process.destroy();
throw new RuntimeException("LGTM analysis timed out");
}
result.setCompletedAt(new Date());
result.setExitCode(process.exitValue());
// Read results
String output = readProcessOutput(process);
result.setRawOutput(output);
if (process.exitValue() == 0) {
result.setSuccess(true);
// Parse SARIF output
result.setSarifResults(parseSarifOutput(output));
} else {
result.setSuccess(false);
result.setError(output);
}
return result;
}
private AnalysisResult waitForAnalysisCompletion() throws Exception {
long startTime = System.currentTimeMillis();
long timeout = analysisTimeoutSeconds * 1000L;
while (System.currentTimeMillis() - startTime < timeout) {
AnalysisResult result = lgtmClient.getProjectAnalysis(projectId);
if ("completed".equals(result.getStatus())) {
return result;
} else if ("failed".equals(result.getStatus())) {
throw new RuntimeException("LGTM analysis failed");
}
// Wait before polling again
Thread.sleep(10000); // 10 seconds
}
throw new RuntimeException("LGTM analysis timed out");
}
private List<Alert> getNewAlerts(String baseCommit, String headCommit) throws Exception {
// This would compare alerts between two commits
// Implementation depends on LGTM API capabilities
return new ArrayList<>();
}
private List<Alert> getFixedAlerts(String baseCommit, String headCommit) throws Exception {
// This would identify alerts that were fixed between commits
return new ArrayList<>();
}
private void postPRComments(String repo, int prNumber, List<Alert> alerts) throws Exception {
StringBuilder comment = new StringBuilder();
comment.append("## LGTM Analysis Results\n\n");
comment.append("Found ").append(alerts.size()).append(" new alerts:\n\n");
for (Alert alert : alerts) {
comment.append(String.format("### %s\n", alert.getSeverity().toUpperCase()));
comment.append(String.format("**File:** %s (Line %d)\n", alert.getFile(), alert.getLine()));
comment.append(String.format("**Message:** %s\n", alert.getMessage()));
comment.append(String.format("**Category:** %s\n\n", alert.getCategory()));
}
githubService.createPRComment(repo, prNumber, comment.toString());
}
private void updatePRStatus(String repo, int prNumber, boolean passed) throws Exception {
String status = passed ? "success" : "failure";
String description = passed ? "No new alerts found" : "New alerts found";
githubService.updatePRStatus(repo, prNumber, "lgtm-analysis", status, description);
}
private void sendAnalysisNotifications(PullRequestAnalysis analysis) {
if (!analysis.getNewAlerts().isEmpty()) {
notificationService.sendAlertNotification(analysis);
}
}
private AlertTrend getAlertTrend() throws Exception {
// This would fetch historical data and calculate trends
// Implementation would depend on data storage
return new AlertTrend();
}
private boolean isLGTMCliAvailable() throws Exception {
Process process = Runtime.getRuntime().exec("lgtm --version");
return process.waitFor() == 0;
}
private String readProcessOutput(Process process) throws IOException {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
return reader.lines().collect(Collectors.joining("\n"));
}
}
private SarifReport.Result convertAlertToSarifResult(Alert alert) {
SarifReport.Result result = new SarifReport.Result();
result.setRuleId(alert.getQueryId());
result.setMessage(alert.getMessage());
SarifReport.Location location = new SarifReport.Location();
location.setUri(alert.getFile());
location.setLine(alert.getLine());
result.setLocations(List.of(location));
result.setLevel(mapSeverityToSarifLevel(alert.getSeverity()));
return result;
}
private String mapSeverityToSarifLevel(String severity) {
switch (severity) {
case "error": return "error";
case "warning": return "warning";
default: return "note";
}
}
private Object parseSarifOutput(String output) {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(output, Object.class);
} catch (Exception e) {
logger.warn("Failed to parse SARIF output", e);
return null;
}
}
// Data models
public static class AlertStatistics {
private int totalAlerts;
private int criticalAlerts;
private int warningAlerts;
private int fixedAlerts;
private Map<String, Long> alertsByCategory;
private Map<String, Long> alertsByFile;
// Getters and setters
public int getTotalAlerts() { return totalAlerts; }
public void setTotalAlerts(int totalAlerts) { this.totalAlerts = totalAlerts; }
public int getCriticalAlerts() { return criticalAlerts; }
public void setCriticalAlerts(int criticalAlerts) { this.criticalAlerts = criticalAlerts; }
public int getWarningAlerts() { return warningAlerts; }
public void setWarningAlerts(int warningAlerts) { this.warningAlerts = warningAlerts; }
public int getFixedAlerts() { return fixedAlerts; }
public void setFixedAlerts(int fixedAlerts) { this.fixedAlerts = fixedAlerts; }
public Map<String, Long> getAlertsByCategory() { return alertsByCategory; }
public void setAlertsByCategory(Map<String, Long> alertsByCategory) { this.alertsByCategory = alertsByCategory; }
public Map<String, Long> getAlertsByFile() { return alertsByFile; }
public void setAlertsByFile(Map<String, Long> alertsByFile) { this.alertsByFile = alertsByFile; }
}
public static class PullRequestAnalysis {
private String repository;
private int pullRequestNumber;
private String baseCommit;
private String headCommit;
private List<Alert> newAlerts;
private List<Alert> fixedAlerts;
private Date analyzedAt;
// Getters and setters
public String getRepository() { return repository; }
public void setRepository(String repository) { this.repository = repository; }
public int getPullRequestNumber() { return pullRequestNumber; }
public void setPullRequestNumber(int pullRequestNumber) { this.pullRequestNumber = pullRequestNumber; }
public String getBaseCommit() { return baseCommit; }
public void setBaseCommit(String baseCommit) { this.baseCommit = baseCommit; }
public String getHeadCommit() { return headCommit; }
public void setHeadCommit(String headCommit) { this.headCommit = headCommit; }
public List<Alert> getNewAlerts() { return newAlerts; }
public void setNewAlerts(List<Alert> newAlerts) { this.newAlerts = newAlerts; }
public List<Alert> getFixedAlerts() { return fixedAlerts; }
public void setFixedAlerts(List<Alert> fixedAlerts) { this.fixedAlerts = fixedAlerts; }
public Date getAnalyzedAt() { return analyzedAt; }
public void setAnalyzedAt(Date analyzedAt) { this.analyzedAt = analyzedAt; }
}
public static class AnalysisReport {
private Date generatedAt;
private String projectId;
private String analysisId;
private int totalAlerts;
private AlertStatistics statistics;
private List<Alert> criticalAlerts;
private List<Alert> warningAlerts;
private AlertTrend trend;
// Getters and setters...
}
public static class AlertTrend {
private Map<Date, Integer> dailyCounts;
private double trendSlope;
private boolean improving;
// Getters and setters...
}
public static class LocalAnalysisResult {
private String projectPath;
private Date startedAt;
private Date completedAt;
private int exitCode;
private boolean success;
private String rawOutput;
private String error;
private Object sarifResults;
// Getters and setters...
}
public static class SarifReport {
private String version;
private List<Run> runs;
// Getters and setters...
public static class Run {
private List<Result> results;
// Getters and setters...
}
public static class Result {
private String ruleId;
private String message;
private List<Location> locations;
private String level;
// Getters and setters...
}
public static class Location {
private String uri;
private int line;
// Getters and setters...
}
}
}

5. GitHub Integration Service

package com.example.lgtm.integration;
import org.kohsuke.github.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.List;
@Service
public class GitHubService {
@Value("${github.token:}")
private String githubToken;
private GitHub github;
public GitHub connect() throws IOException {
if (github == null) {
github = new GitHubBuilder().withOAuthToken(githubToken).build();
}
return github;
}
public PullRequestDetails getPullRequestDetails(String repo, int prNumber) throws IOException {
GHRepository repository = connect().getRepository(repo);
GHPullRequest pr = repository.getPullRequest(prNumber);
PullRequestDetails details = new PullRequestDetails();
details.setNumber(prNumber);
details.setTitle(pr.getTitle());
details.setBaseCommit(pr.getBase().getSha());
details.setHeadCommit(pr.getHead().getSha());
details.setAuthor(pr.getUser().getLogin());
return details;
}
public void createPRComment(String repo, int prNumber, String comment) throws IOException {
GHRepository repository = connect().getRepository(repo);
GHPullRequest pr = repository.getPullRequest(prNumber);
pr.comment(comment);
}
public void updatePRStatus(String repo, int prNumber, String context, String state, String description) throws IOException {
GHRepository repository = connect().getRepository(repo);
GHPullRequest pr = repository.getPullRequest(prNumber);
pr.getHead().createCommitStatus(
GHCommitState.valueOf(state.toUpperCase()),
null,
description,
context
);
}
public List<GHPullRequest> getOpenPullRequests(String repo) throws IOException {
GHRepository repository = connect().getRepository(repo);
return repository.getPullRequests(GHIssueState.OPEN);
}
public static class PullRequestDetails {
private int number;
private String title;
private String baseCommit;
private String headCommit;
private String author;
// Getters and setters
public int getNumber() { return number; }
public void setNumber(int number) { this.number = number; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getBaseCommit() { return baseCommit; }
public void setBaseCommit(String baseCommit) { this.baseCommit = baseCommit; }
public String getHeadCommit() { return headCommit; }
public void setHeadCommit(String headCommit) { this.headCommit = headCommit; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
}
}

6. CI/CD Integration

GitHub Actions Workflow

# .github/workflows/lgtm-analysis.yml
name: LGTM Analysis
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
lgtm-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Java
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn -B compile test-compile -DskipTests
- name: Run LGTM Analysis
uses: lgtm-com/lgtm-action@v1
with:
project-token: ${{ secrets.LGTM_PROJECT_TOKEN }}
output-format: sarif
output-file: lgtm-results.sarif
- name: Upload SARIF results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: lgtm-results.sarif
- name: Check for critical alerts
run: |
# Custom script to check results
python scripts/check-lgtm-results.py
- name: Notify on critical issues
if: failure()
uses: actions/github-script@v6
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '🚨 Critical LGTM alerts found! Please review the analysis results.'
})

Jenkins Pipeline

// Jenkinsfile
pipeline {
agent any
environment {
LGTM_PROJECT_TOKEN = credentials('lgtm-project-token')
}
stages {
stage('Build') {
steps {
sh 'mvn clean compile test-compile -DskipTests'
}
}
stage('LGTM Analysis') {
steps {
sh '''
# Install LGTM CLI
curl -s https://lgtm.com/ci/lgtm-cli-installer.sh | sh
# Run analysis
lgtm analyze --format sarif --output lgtm-results.sarif .
# Check results
python scripts/check_lgtm_results.py
'''
}
post {
always {
archiveArtifacts artifacts: 'lgtm-results.sarif', fingerprint: true
publishSARIF filePattern: 'lgtm-results.sarif'
}
failure {
emailext (
subject: "LGTM Analysis Failed - ${env.JOB_NAME}",
body: "Critical alerts found in ${env.BUILD_URL}",
to: "[email protected]"
)
}
}
}
}
}

7. REST API Controllers

package com.example.lgtm.controller;
import com.example.lgtm.service.LGTMAnalysisService;
import com.example.lgtm.service.LGTMAnalysisService.AnalysisReport;
import com.example.lgtm.service.LGTMAnalysisService.AlertStatistics;
import com.example.lgtm.service.LGTMAnalysisService.PullRequestAnalysis;
import com.example.lgtm.client.LGTMApiClient.Alert;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/lgtm")
public class LGTMController {
private final LGTMAnalysisService analysisService;
public LGTMController(LGTMAnalysisService analysisService) {
this.analysisService = analysisService;
}
@PostMapping("/analyze")
public ResponseEntity<String> triggerAnalysis() {
try {
analysisService.triggerAndWaitForAnalysis();
return ResponseEntity.ok("Analysis completed successfully");
} catch (Exception e) {
return ResponseEntity.internalServerError().body("Analysis failed: " + e.getMessage());
}
}
@GetMapping("/alerts")
public ResponseEntity<List<Alert>> getAlerts(@RequestParam(required = false) String severity) {
try {
List<Alert> alerts = analysisService.getAlertsBySeverity(severity);
return ResponseEntity.ok(alerts);
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@GetMapping("/alerts/statistics")
public ResponseEntity<AlertStatistics> getAlertStatistics() {
try {
AlertStatistics stats = analysisService.getAlertStatistics();
return ResponseEntity.ok(stats);
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@GetMapping("/alerts/categories")
public ResponseEntity<Map<String, List<Alert>>> getAlertsByCategory() {
try {
Map<String, List<Alert>> alertsByCategory = analysisService.getAlertsByCategory();
return ResponseEntity.ok(alertsByCategory);
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@PostMapping("/pr/{repo}/{prNumber}/analyze")
public ResponseEntity<PullRequestAnalysis> analyzePullRequest(
@PathVariable String repo, 
@PathVariable int prNumber) {
try {
PullRequestAnalysis analysis = analysisService.analyzePullRequest(repo, prNumber);
return ResponseEntity.ok(analysis);
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@GetMapping("/report")
public ResponseEntity<AnalysisReport> generateReport() {
try {
AnalysisReport report = analysisService.generateReport();
return ResponseEntity.ok(report);
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@GetMapping("/export/sarif")
public ResponseEntity<String> exportToSarif() {
try {
String sarif = analysisService.exportToSarif();
return ResponseEntity.ok()
.header("Content-Type", "application/sarif+json")
.header("Content-Disposition", "attachment; filename=lgtm-results.sarif")
.body(sarif);
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
@GetMapping("/health")
public ResponseEntity<Map<String, String>> healthCheck() {
try {
analysisService.getCurrentAnalysis();
return ResponseEntity.ok(Map.of("status", "healthy"));
} catch (Exception e) {
return ResponseEntity.status(503)
.body(Map.of("status", "unhealthy", "error", e.getMessage()));
}
}
}

8. Application Configuration

# application.yml
lgtm:
api:
token: ${LGTM_API_TOKEN}
base-url: https://lgtm.com/api/v1.0
project:
id: ${LGTM_PROJECT_ID}
analysis:
timeout: 600
fail-on-critical: true
github:
token: ${GITHUB_TOKEN}
notification:
slack:
webhook-url: ${SLACK_WEBHOOK_URL}
email:
enabled: false
recipients: [email protected]
logging:
level:
com.example.lgtm: DEBUG

Best Practices

1. Query Optimization

// Custom query for specific project patterns
public class CustomQueries {
/**
* Query to detect unsafe file operations
*/
public static final String UNSAFE_FILE_OPERATION = """
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
from MethodAccess fileOp, Expr userInput
where
fileOp.getMethod().getName().matches("delete|rename|create") and
fileOp.getMethod().getDeclaringType().hasQualifiedName("java.io", "File") and
DataFlow::localFlow(DataFlow::parameterNode(userInput), DataFlow::exprNode(fileOp.getArgument(0)))
select fileOp, "Unsafe file operation with user input: $@", userInput, userInput.toString()
""";
}

2. Alert Management

@Component
public class AlertManagementService {
public void autoCloseFalsePositives(List<Alert> alerts) {
// Auto-close known false positives
alerts.stream()
.filter(this::isFalsePositive)
.forEach(this::closeAlert);
}
private boolean isFalsePositive(Alert alert) {
// Implement false positive detection logic
return alert.getFile().contains("/test/") ||
alert.getMessage().contains("known false positive");
}
}

Conclusion

This comprehensive LGTM integration provides:

  • Automated code analysis with LGTM.com
  • CI/CD integration with GitHub Actions and Jenkins
  • Pull request analysis with automatic comments
  • Custom query development for project-specific patterns
  • Comprehensive reporting with statistics and trends
  • SARIF export for tool integration
  • REST API for programmatic access

Key benefits:

  • Continuous code quality monitoring
  • Security vulnerability detection
  • Developer workflow integration
  • Customizable analysis with project-specific queries
  • Comprehensive reporting and trend analysis

This setup ensures code quality and security are maintained throughout the development lifecycle, with immediate feedback to developers and comprehensive reporting for teams.

Secure Java Dependency Management, Vulnerability Scanning & Software Supply Chain Protection (SBOM, SCA, CI Security & License Compliance)

https://macronepal.com/blog/github-code-scanning-in-java-complete-guide/
Explains GitHub Code Scanning for Java using tools like CodeQL to automatically analyze source code and detect security vulnerabilities directly inside CI/CD pipelines before deployment.

https://macronepal.com/blog/license-compliance-in-java-comprehensive-guide/
Explains software license compliance in Java projects, ensuring dependencies follow legal requirements (MIT, Apache, GPL, etc.) and preventing license violations in enterprise software.

https://macronepal.com/blog/container-security-for-java-uncovering-vulnerabilities-with-grype/
Explains using Grype to scan Java container images and filesystems for known CVEs in OS packages and application dependencies to improve container security.

https://macronepal.com/blog/syft-sbom-generation-in-java-comprehensive-software-bill-of-materials-for-jvm-applications/
Explains using Syft to generate SBOMs (Software Bill of Materials) for Java applications, listing all dependencies, libraries, and components for supply chain transparency.

https://macronepal.com/blog/comprehensive-dependency-analysis-generating-and-scanning-sboms-with-trivy-for-java/
Explains using Trivy to generate SBOMs and scan Java dependencies and container images for vulnerabilities, integrating security checks into CI/CD pipelines.

https://macronepal.com/blog/dependabot-for-java-in-java/
Explains GitHub Dependabot for Java projects, which automatically detects vulnerable dependencies and creates pull requests to update them securely.

https://macronepal.com/blog/parasoft-jtest-in-java-comprehensive-guide-to-code-analysis-and-testing/
Explains Parasoft Jtest, a static analysis and testing tool for Java that helps detect bugs, security issues, and code quality problems early in development.

https://macronepal.com/blog/snyk-open-source-in-java-comprehensive-dependency-vulnerability-management-2/
Explains Snyk Open Source for Java, which continuously scans dependencies for vulnerabilities and provides automated fix suggestions and monitoring.

https://macronepal.com/blog/owasp-dependency-check-in-java-complete-vulnerability-scanning-guide/
Explains OWASP Dependency-Check, which scans Java dependencies against the National Vulnerability Database (NVD) to detect known security vulnerabilities.

https://macronepal.com/blog/securing-your-dependencies-a-java-developers-guide-to-whitesource-mend-bolt/
Explains Mend (WhiteSource) Bolt for Java, a dependency management and SCA tool that provides vulnerability detection, license compliance, and security policy enforcement in enterprise environments.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper