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.