Introduction to DeepSource
DeepSource is a static analysis tool that automatically analyzes code changes and identifies issues affecting code quality, security, and performance. It supports Java with a wide range of analyzers and customizable rules.
Configuration
.deepsource.toml Configuration
version = 1 [[analyzers]] name = "java" enabled = true
[analyzers.meta]
runtime_version = "17" # Anti-patterns and bug risks
[analyzers.patterns.bug_risk]
avoid_default_interface_methods = true avoid_float_equality_comparison = true avoid_print_stack_trace = true avoid_system_exit = true avoid_thread_sleep = true avoid_volatile_for_arrays = true cloneable_without_super_clone = true compare_to_uses_object_equality = true equals_uses_objects_hash = true missing_override_annotation = true unused_method_parameter = true # Security issues
[analyzers.patterns.security]
avoid_dynamic_sql = true avoid_file_stream = true avoid_hard_coded_config = true avoid_http_proxy = true avoid_insecure_cipher = true avoid_java_deserialization = true avoid_reflection = true avoid_ssl_disabled = true avoid_trust_all_certificates = true path_traversal = true sql_injection = true # Performance issues
[analyzers.patterns.performance]
avoid_boxed_primitives_creation = true avoid_string_concat_in_loop = true avoid_unnecessary_string_creation = true inefficient_string_usage = true prefer_string_buffer = true use_character_parameterized_method = true # Style issues
[analyzers.patterns.style]
avoid_static_import = true avoid_wildcard_import = true constant_naming_convention = true default_package = true empty_statement = true field_naming_convention = true local_variable_naming_convention = true method_naming_convention = true parameter_naming_convention = true type_naming_convention = true unused_import = true [[transformers]] name = "java-format" enabled = true [[transformers]] name = "java-dependency-graph" enabled = true # Exclude patterns
[exclude]
paths = [ "**/test/**", "**/generated/**", "**/target/**", "**/build/**" ] # Custom issue configurations
[issues]
ignore = [ # Temporarily ignore specific issues "java/bug-risk/avoid-thread-sleep", "java/style/unused-import" ]
[issues.severity]
# Override default severities "java/security/avoid-ssl-disabled" = "high" "java/security/sql-injection" = "high" "java/bug-risk/avoid-system-exit" = "medium" "java/style/unused-import" = "low"
Common Issues Detected
Security Issues
import java.sql.*;
import java.io.*;
public class SecurityAntiPatterns {
// DEEPSOURCE SCAN: java/security/sql-injection
public void sqlInjectionVulnerability(String userInput) throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/test");
Statement stmt = conn.createStatement();
// Vulnerable to SQL injection
String query = "SELECT * FROM users WHERE name = '" + userInput + "'";
ResultSet rs = stmt.executeQuery(query); // DEEPSOURCE: SQL injection risk
// Secure alternative
String secureQuery = "SELECT * FROM users WHERE name = ?";
PreparedStatement pstmt = conn.prepareStatement(secureQuery);
pstmt.setString(1, userInput);
ResultSet secureRs = pstmt.executeQuery();
}
// DEEPSOURCE SCAN: java/security/path-traversal
public void pathTraversalVulnerability(String fileName) throws IOException {
// Vulnerable to path traversal
File file = new File("/uploads/" + fileName); // DEEPSOURCE: Path traversal risk
FileInputStream fis = new FileInputStream(file);
// Secure alternative
String safeFileName = sanitizeFileName(fileName);
File safeFile = new File("/uploads/" + safeFileName);
FileInputStream safeFis = new FileInputStream(safeFile);
}
private String sanitizeFileName(String fileName) {
// Remove path traversal sequences
return fileName.replaceAll("\\.\\.", "").replaceAll("/", "").replaceAll("\\\\", "");
}
// DEEPSOURCE SCAN: java/security/avoid-ssl-disabled
public void insecureSSLConfiguration() {
// Disabling SSL verification
System.setProperty("https.protocols", "TLSv1"); // DEEPSOURCE: Insecure SSL configuration
// Trust all certificates (security risk)
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
public X509Certificate[] getAcceptedIssuers() { return null; }
}
};
}
// DEEPSOURCE SCAN: java/security/avoid-java-deserialization
public void insecureDeserialization(byte[] data) throws Exception {
// Insecure deserialization
ByteArrayInputStream bis = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bis);
Object obj = ois.readObject(); // DEEPSOURCE: Java deserialization risk
}
// DEEPSOURCE SCAN: java/security/avoid-hard-coded-config
public void hardcodedSecrets() {
String password = "mySecretPassword123!"; // DEEPSOURCE: Hard-coded secret
String apiKey = "sk_live_1234567890abcdef"; // DEEPSOURCE: Hard-coded secret
// Better approach - use environment variables or secure config
String securePassword = System.getenv("DB_PASSWORD");
String secureApiKey = System.getenv("STRIPE_API_KEY");
}
}
Performance Issues
import java.util.*;
import java.util.stream.Collectors;
public class PerformanceAntiPatterns {
// DEEPSOURCE SCAN: java/performance/avoid-string-concat-in-loop
public String inefficientStringConcatenation(List<String> items) {
String result = "";
for (String item : items) {
result += item; // DEEPSOURCE: String concatenation in loop
}
return result;
// Efficient alternative
// StringBuilder sb = new StringBuilder();
// for (String item : items) {
// sb.append(item);
// }
// return sb.toString();
// Or using streams
// return items.stream().collect(Collectors.joining());
}
// DEEPSOURCE SCAN: java/performance/avoid-boxed-primitives-creation
public void unnecessaryBoxing() {
// Unnecessary boxing
Integer count = new Integer(42); // DEEPSOURCE: Boxed primitive creation
Long timestamp = new Long(System.currentTimeMillis()); // DEEPSOURCE: Boxed primitive creation
// Better - use primitive or valueOf
int primitiveCount = 42;
Integer cachedCount = Integer.valueOf(42);
long primitiveTimestamp = System.currentTimeMillis();
}
// DEEPSOURCE SCAN: java/performance/inefficient-string-usage
public void inefficientStringOperations() {
String text = "Hello World";
// Inefficient - creates intermediate strings
String result = text.trim().toLowerCase().replace(" ", "-"); // DEEPSOURCE: Inefficient string usage
// More efficient - chain operations thoughtfully
String efficientResult = text.trim();
efficientResult = efficientResult.toLowerCase();
efficientResult = efficientResult.replace(" ", "-");
}
// DEEPSOURCE SCAN: java/performance/prefer-string-buffer
public void threadSafeStringBuilding() {
// StringBuffer is thread-safe but slower
StringBuffer buffer = new StringBuffer(); // DEEPSOURCE: Prefer StringBuilder for local use
buffer.append("Hello");
buffer.append("World");
// Use StringBuilder for local variables (faster)
StringBuilder builder = new StringBuilder();
builder.append("Hello");
builder.append("World");
}
// DEEPSOURCE SCAN: java/performance/use-character-parameterized-method
public void characterParameterIssues() {
String text = "Hello World";
// Inefficient - creates String object
text.indexOf("W"); // DEEPSOURCE: Use character parameterized method
// Efficient - uses char
text.indexOf('W');
// Similarly for other methods
text.replace("l", "L"); // Creates String
text.replace('l', 'L'); // Uses char
}
}
Bug Risk Issues
import java.util.*;
public class BugRiskAntiPatterns {
// DEEPSOURCE SCAN: java/bug-risk/avoid-float-equality-comparison
public void floatingPointComparison() {
double a = 0.1 + 0.2;
double b = 0.3;
// Dangerous float comparison
if (a == b) { // DEEPSOURCE: Avoid float equality comparison
System.out.println("Equal");
}
// Safe comparison
final double EPSILON = 0.0001;
if (Math.abs(a - b) < EPSILON) {
System.out.println("Effectively equal");
}
}
// DEEPSOURCE SCAN: java/bug-risk/avoid-thread-sleep
public void threadSleepInBusinessLogic() throws InterruptedException {
// Thread.sleep in business logic - usually a code smell
Thread.sleep(1000); // DEEPSOURCE: Avoid Thread.sleep
// Better alternatives:
// - Use ScheduledExecutorService for scheduled tasks
// - Use CompletableFuture.delayedExecutor() for delays
// - Use proper concurrency primitives (CountDownLatch, CyclicBarrier)
}
// DEEPSOURCE SCAN: java/bug-risk/avoid-system-exit
public void systemExitInApplication() {
// System.exit in application code - prevents proper shutdown
if (criticalError) {
System.exit(1); // DEEPSOURCE: Avoid System.exit
}
// Better approach - throw exception or use application lifecycle
if (criticalError) {
throw new CriticalFailureException("Application cannot continue");
}
}
// DEEPSOURCE SCAN: java/bug-risk/avoid-print-stack-trace
public void exceptionHandling() {
try {
riskyOperation();
} catch (Exception e) {
e.printStackTrace(); // DEEPSOURCE: Avoid printStackTrace
// Better: Use proper logging
logger.error("Operation failed", e);
}
}
// DEEPSOURCE SCAN: java/bug-risk/missing-override-annotation
public class BaseClass {
public void doSomething() {
// Base implementation
}
}
public class DerivedClass extends BaseClass {
public void doSomething() { // DEEPSOURCE: Missing @Override annotation
// Derived implementation
}
@Override // Correct
public void doSomething() {
// Derived implementation
}
}
// DEEPSOURCE SCAN: java/bug-risk/unused-method-parameter
public void unusedParameter(String used, String unused) { // DEEPSOURCE: Unused method parameter
System.out.println(used);
// 'unused' parameter is never used
}
// DEEPSOURCE SCAN: java/bug-risk/compare-to-uses-object-equality
public class Person implements Comparable<Person> {
private String name;
private int age;
@Override
public int compareTo(Person other) {
if (this == other) return 0; // DEEPSOURCE: CompareTo uses object equality
return Integer.compare(this.age, other.age);
}
// Correct implementation
@Override
public int compareTo(Person other) {
return Integer.compare(this.age, other.age);
}
}
}
Custom Rules and Suppressions
Custom DeepSource Analyzer
// Custom analyzer for business-specific rules
public class CustomBusinessRulesAnalyzer {
// Custom rule: Ensure repository methods follow naming conventions
public static void validateRepositoryMethodNames(MethodDeclaration method) {
String methodName = method.getName().toString();
if (isRepositoryInterface(method.getParent()) &&
!followsRepositoryNamingConvention(methodName)) {
reportIssue(method,
"Repository method names should follow Spring Data conventions",
"Use prefixes: find, read, get, query, search, count, exists, delete, remove");
}
}
// Custom rule: Validate @Transactional usage
public static void validateTransactionalUsage(MethodDeclaration method) {
boolean hasTransactional = hasAnnotation(method, "Transactional");
boolean modifiesData = methodNameIndicatesModification(method.getName().toString());
if (modifiesData && !hasTransactional) {
reportIssue(method,
"Data modification methods should be @Transactional",
"Add @Transactional annotation to ensure transaction boundaries");
}
}
// Custom rule: Check for proper resource cleanup
public static void validateResourceCleanup(MethodDeclaration method) {
List<VariableDeclaration> closeableVars = findAutoCloseableVariables(method);
for (VariableDeclaration var : closeableVars) {
if (!isInTryWithResources(var)) {
reportIssue(var,
"AutoCloseable resources should use try-with-resources",
"Wrap resource usage in try-with-resources statement");
}
}
}
private static boolean isRepositoryInterface(Node parent) {
return parent instanceof ClassDeclaration &&
((ClassDeclaration) parent).getImplements()
.stream()
.anyMatch(impl -> impl.toString().contains("Repository"));
}
private static boolean followsRepositoryNamingConvention(String methodName) {
return methodName.matches("^(find|read|get|query|search|count|exists|delete|remove)(By|And|Or|OrderBy).*");
}
}
Issue Suppression
import java.sql.*;
public class SuppressionExamples {
// DeepSource ignore for specific methods
// deepsource ignore: java/security/sql-injection
public void safeDynamicQuery(String tableName) throws SQLException {
// This is safe because we control the table name
String query = "SELECT COUNT(*) FROM " + tableName;
// ... execute query
}
// DeepSource ignore with reason
// deepsource ignore: java/bug-risk/avoid-thread-sleep: Required for legacy integration
public void legacyIntegration() throws InterruptedException {
Thread.sleep(500); // Required for legacy system
}
// File-level suppression
// deepsource ignore-file: java/performance/avoid-string-concat-in-loop
public class LegacyCode {
// All string concatenation in this file is ignored
public String buildMessage(String part1, String part2) {
return part1 + " " + part2; // This won't trigger DeepSource
}
}
// Method-level suppression with multiple rules
// deepsource ignore: java/security/avoid-hard-coded-config,java/performance/avoid-boxed-primitives-creation
public void testConfiguration() {
String testPassword = "test123"; // OK in test context
Integer testCount = new Integer(42); // OK in test context
}
}
Integration with Build Tools
Maven Configuration
<project> <build> <plugins> <!-- DeepSource Maven Plugin --> <plugin> <groupId>io.deepsource</groupId> <artifactId>deepsource-maven-plugin</artifactId> <version>1.0.0</version> <configuration> <analyzer>java</analyzer> <configFile>.deepsource.toml</configFile> <reportFormat>json</reportFormat> <failOnHighSeverity>true</failOnHighSeverity> </configuration> <executions> <execution> <goals> <goal>analyze</goal> </goals> <phase>verify</phase> </execution> </executions> </plugin> <!-- Ensure Java 17 for DeepSource --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>17</source> <target>17</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> <reporting> <plugins> <!-- DeepSource Report --> <plugin> <groupId>io.deepsource</groupId> <artifactId>deepsource-maven-plugin</artifactId> <version>1.0.0</version> <reportSets> <reportSet> <reports> <report>report</report> </reports> </reportSet> </reportSets> </plugin> </plugins> </reporting> </project>
Gradle Configuration
plugins {
id 'java'
id 'io.deepsource.gradle' version '1.0.0'
}
deepsource {
analyzer = 'java'
configFile = '.deepsource.toml'
reportFormat = 'json'
failOnHighSeverity = true
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.named('deepsourceAnalyze') {
dependsOn compileJava
}
CI/CD Integration
GitHub Actions Workflow
name: DeepSource Analysis
on: [push, pull_request]
jobs:
deepsource:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Cache Maven dependencies
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Run DeepSource Analysis
run: mvn deepsource:analyze
- name: Upload DeepSource Report
uses: actions/upload-artifact@v3
with:
name: deepsource-report
path: target/deepsource-report.json
- name: DeepSource Autofix
uses: deepsource/autofix-action@v2
with:
deepsource-token: ${{ secrets.DEEPSOURCE_TOKEN }}
GitLab CI Configuration
stages: - test - analysis deepSource_analysis: stage: analysis image: maven:3.8.6-openjdk-17 script: - mvn deepsource:analyze artifacts: paths: - target/deepsource-report.json when: always only: - merge_requests - main - develop
Best Practices Implementation
Secure Coding Patterns
import java.sql.*;
import java.util.regex.Pattern;
public class SecureCodingPatterns {
// Input validation
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
public boolean validateEmail(String email) {
return email != null && EMAIL_PATTERN.matcher(email).matches();
}
// Secure SQL execution
public User findUserById(Long userId) throws SQLException {
String sql = "SELECT * FROM users WHERE id = ? AND status = 'ACTIVE'";
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setLong(1, userId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return mapResultSetToUser(rs);
}
}
}
return null;
}
// Secure file operations
public void processUploadedFile(String fileName, InputStream fileStream) throws IOException {
// Validate file name
if (!isSafeFileName(fileName)) {
throw new SecurityException("Invalid file name");
}
// Use safe path
Path safePath = Paths.get("uploads", sanitizeFileName(fileName));
// Copy file securely
Files.copy(fileStream, safePath, StandardCopyOption.REPLACE_EXISTING);
}
private boolean isSafeFileName(String fileName) {
return fileName != null &&
!fileName.contains("..") &&
!fileName.contains("/") &&
!fileName.contains("\\") &&
fileName.matches("[a-zA-Z0-9._-]+");
}
// Secure password handling
public boolean verifyPassword(String inputPassword, String storedHash) {
return PasswordEncoder.verify(inputPassword, storedHash);
}
}
Performance Optimization Patterns
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class PerformanceOptimizationPatterns {
// Efficient collection usage
private final Map<String, User> userCache = new ConcurrentHashMap<>();
public User getUser(String userId) {
return userCache.computeIfAbsent(userId, this::loadUserFromDatabase);
}
// StringBuilder for string building
public String buildCsvRow(List<String> cells) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < cells.size(); i++) {
if (i > 0) {
sb.append(',');
}
sb.append(escapeCsv(cells.get(i)));
}
sb.append('\n');
return sb.toString();
}
// Efficient stream usage
public List<String> getActiveUserNames(List<User> users) {
return users.stream()
.filter(User::isActive)
.map(User::getName)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
// Avoid unnecessary object creation
private static final Pattern PHONE_PATTERN =
Pattern.compile("^\\+?[1-9]\\d{1,14}$");
public boolean isValidPhoneNumber(String phone) {
return phone != null && PHONE_PATTERN.matcher(phone).matches();
}
// Use primitives when possible
public long calculateSum(List<Integer> numbers) {
long sum = 0L; // Use long to avoid overflow
for (int number : numbers) {
sum += number;
}
return sum;
}
}
Monitoring and Reporting
Custom Analysis Reporting
import com.fasterxml.jackson.databind.ObjectMapper;
import java.nio.file.Files;
import java.nio.file.Path;
public class DeepSourceReportGenerator {
private final ObjectMapper mapper = new ObjectMapper();
public void generateCustomReport(Path deepsourceReport, Path customReport) throws IOException {
DeepSourceReport report = mapper.readValue(
Files.readString(deepsourceReport),
DeepSourceReport.class
);
CustomReport custom = analyzeReport(report);
mapper.writerWithDefaultPrettyPrinter()
.writeValue(customReport.toFile(), custom);
}
private CustomReport analyzeReport(DeepSourceReport report) {
CustomReport custom = new CustomReport();
// Categorize issues by severity and type
Map<String, Long> issuesBySeverity = report.getIssues().stream()
.collect(Collectors.groupingBy(
Issue::getSeverity,
Collectors.counting()
));
Map<String, Long> issuesByCategory = report.getIssues().stream()
.collect(Collectors.groupingBy(
issue -> issue.getCategory() + "/" + issue.getType(),
Collectors.counting()
));
custom.setIssuesBySeverity(issuesBySeverity);
custom.setIssuesByCategory(issuesByCategory);
custom.setTotalIssues(report.getIssues().size());
custom.setCriticalIssues(countCriticalIssues(report));
return custom;
}
private long countCriticalIssues(DeepSourceReport report) {
return report.getIssues().stream()
.filter(issue -> "HIGH".equals(issue.getSeverity()))
.filter(this::isSecurityIssue)
.count();
}
private boolean isSecurityIssue(Issue issue) {
return issue.getCategory().equals("security") ||
issue.getType().contains("injection") ||
issue.getType().contains("traversal") ||
issue.getType().contains("deserialization");
}
public static class CustomReport {
private Map<String, Long> issuesBySeverity;
private Map<String, Long> issuesByCategory;
private long totalIssues;
private long criticalIssues;
// Getters and setters
public Map<String, Long> getIssuesBySeverity() { return issuesBySeverity; }
public void setIssuesBySeverity(Map<String, Long> issuesBySeverity) {
this.issuesBySeverity = issuesBySeverity;
}
public Map<String, Long> getIssuesByCategory() { return issuesByCategory; }
public void setIssuesByCategory(Map<String, Long> issuesByCategory) {
this.issuesByCategory = issuesByCategory;
}
public long getTotalIssues() { return totalIssues; }
public void setTotalIssues(long totalIssues) { this.totalIssues = totalIssues; }
public long getCriticalIssues() { return criticalIssues; }
public void setCriticalIssues(long criticalIssues) { this.criticalIssues = criticalIssues; }
}
}
Conclusion
DeepSource provides comprehensive static analysis for Java with:
- Security Analysis - SQL injection, path traversal, insecure configurations
- Performance Optimization - String operations, boxing, collection usage
- Bug Risk Detection - Common programming errors and anti-patterns
- Code Style Enforcement - Naming conventions, imports, structure
- Custom Rule Support - Business-specific analysis rules
- CI/CD Integration - GitHub Actions, GitLab CI, and other platforms
- Auto-fix Capabilities - Automatic fixes for many issue types
By integrating DeepSource into your development workflow, you can catch issues early, maintain code quality, and enforce security best practices across your Java codebase.
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.