Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
void main() {
IO.println("compact source");
}

int main(int i) { // Noncompliant
return i * 2;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package checks.mainSignature;

interface NonInstantiable {
int multiply(int a, int b);

default void main() { // Compliant
System.out.println("default");
}

int main(int a); // Noncompliant

static void main(String[] arg) { // Compliant
System.out.println("yep, inside an interface");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package checks.mainSignature;

public class PrivateMain {
private static void main(String[] args) { // Noncompliant
}

private void main() { // Noncompliant
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package checks.mainSignature;

public class Sample {
public static class Traditional {
public static void main(String[] args) { // Compliant
System.out.println("traditional");
}
}

public static class NoArg {
public static void main() { // Compliant
System.out.println("no arg");
}
}

public static class Instance {
void main(String[] args) { // Compliant
System.out.println("instance");
}
}

public static class Wrong {
public static void main(String[] args) {
System.out.println(new Wrong().main(42));
}

int main(int x) { // Noncompliant
return x * x;
}

String main(String x, String y) { // Noncompliant
return "";
}
}

public static class WrongReturn {
public static int main(String[] args) { // Noncompliant
return 1;
}
}

@interface MyAnnotation {
String main() default ""; // Noncompliant
}

enum MyEnum {
A, B;

void main(int x) { // Noncompliant
}
}

record MyRecord(int value) {
void main(int x) { // Noncompliant
}

static void main(String[] args) {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package checks.mainSignature;

public class Varargs {
void main(String... args) {
}

void main(int i, String ... args) { // Noncompliant

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package checks.mainSignature;

// This one is really weird. Doesn't really matter what we report,
// as long as nothing crashes.

public class main {
public main() {}

public main(String[] args) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* SonarQube Java
* Copyright (C) 2012-2025 SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;

import java.util.List;
import org.sonar.check.Rule;
import org.sonar.java.model.ModifiersUtils;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.Modifier;
import org.sonar.plugins.java.api.tree.ArrayTypeTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.TypeTree;
import org.sonar.plugins.java.api.tree.VariableTree;


/**
* Checks that the "main" method has the correct signature for a program entry point.
*
* <p>Note, that even with a correct signature, the "main" method may not be valid entry point.
* For example, it may be declared in an abstract class or an interface.
*/
@Rule(key = "S3051")
public class MainMethodSignatureCheck extends IssuableSubscriptionVisitor {

private static final String MESSAGE = "\"main\" method should only be used for the program entry point and should have appropriate signature.";

@Override
public List<Tree.Kind> nodesToVisit() {
return List.of(Tree.Kind.METHOD);
}

@Override
public void visitNode(Tree tree) {
MethodTree methodTree = (MethodTree) tree;
if (!"main".equals(methodTree.simpleName().name())) {
return;
}
if (!isValidMainSignature(methodTree)) {
reportIssue(methodTree.simpleName(), MESSAGE);
}
}

/**
* Checks if the signature is a valid "main" method signature in Java 25.
*
* @return true, false, or null if it cannot be determined
*/
private static boolean isValidMainSignature(MethodTree methodTree) {
// main cannot be private
if (ModifiersUtils.hasModifier(methodTree.modifiers(), Modifier.PRIVATE)) {
return false;
}

// return type must be void
TypeTree returnType = methodTree.returnType();
// null check just to avoid NPE
if (returnType == null || !returnType.symbolType().isVoid()) {
return false;
}

List<VariableTree> parameters = methodTree.parameters();
// no arguments or single String[] argument
return parameters.isEmpty() ||
(parameters.size() == 1
&& parameters.get(0).type() instanceof ArrayTypeTree att
&& "String".equals(att.type().symbolType().name()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* SonarQube Java
* Copyright (C) 2012-2025 SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;

import org.junit.jupiter.api.Test;
import org.sonar.java.checks.verifier.CheckVerifier;

import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;

class MainMethodSignatureCheckTest {

private static final MainMethodSignatureCheck CHECK = new MainMethodSignatureCheck();

@Test
void test() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath("checks/mainSignature/Sample.java"))
.withCheck(CHECK)
.verifyIssues();
}

@Test
void nonInstantiable() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath("checks/mainSignature/NonInstantiable.java"))
.withCheck(CHECK)
.verifyIssues();
}

@Test
void privateMain() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath("checks/mainSignature/PrivateMain.java"))
.withCheck(CHECK)
.verifyIssues();
}

@Test
void compactSource() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath("checks/mainSignature/CompactSource.java"))
.withCheck(CHECK)
.verifyIssues();
}

@Test
void varargs() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath("checks/mainSignature/Varargs.java"))
.withCheck(CHECK)
.verifyIssues();
}

@Test
void constructor() {
// The check will not run, because it does not visit constructor nodes.
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath("checks/mainSignature/main.java"))
.withCheck(CHECK)
.verifyNoIssues();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<h2>Why is this an issue?</h2>
<p>"A rose by any other name would smell as sweet," but <code>main</code> by any other name would not. Just because a method has the name "main", that
doesn’t make it the entry point to an application. It must also have the correct signature. Specifically, it must be <code>public static void</code>
and accept a single <code>String []</code> as an argument.</p>
<h3>Noncompliant code example</h3>
<pre>
public void main(String arg) { // Noncompliant
// ...
}
</pre>
<h3>Compliant solution</h3>
<pre>
public static void main(String [] args) {
// ...
}
</pre>

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"title": "\"main\" should have the right signature",
"type": "BUG",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "15min"
},
"tags": [],
"defaultSeverity": "Blocker",
"ruleSpecification": "RSPEC-3051",
"sqKey": "S3051",
"scope": "Main",
"quickfix": "unknown"
}
Loading