Java public Modifier
Java public Modifier
public is the widest access modifier in Java. A member or class marked public is accessible from any class in any package anywhere in the program — no restrictions. It is the declaration that says: this is intentionally part of the outward-facing contract. Callers in the same class, same package, different package, subclass, non-subclass — all of them can reach a public member.
Used carelessly, public creates fragile code where any external class can depend on any internal detail, making changes impossible without breaking callers. Used deliberately, public defines a clean, stable API surface that the rest of the codebase depends on with confidence.
What public Can Be Applied To
public can be placed on:
✓ Top-level class → accessible from any package
✓ Nested / inner class → accessible from any package
✓ Constructor → any code can call new ClassName()
✓ Method (instance) → any code can call obj.method()
✓ Method (static) → any code can call ClassName.method()
✓ Field (instance) → any code can read/write obj.field
✓ Field (static) → any code can read/write ClassName.field
✓ Interface → implemented / used from any package
✓ Enum → used from any package
Rule for top-level classes:
— One public class per source file
— File name must match the public class name exactly
— class OrderService → must be in OrderService.java
1 — public Class
A public class is visible to every class in every package. It can be imported and instantiated from anywhere.
1// File: com/devstackflow/api/ProductCatalogue.java
2package com.devstackflow.api;
3
4import java.util.ArrayList;
5import java.util.List;
6import java.util.Optional;
7
8public class ProductCatalogue { // public — importable from any package
9
10 private final List<String> products = new ArrayList<>();
11
12 // public constructor — any code can create an instance
13 public ProductCatalogue() { }
14
15 public void addProduct(String product) {
16 products.add(product);
17 }
18
19 public Optional<String> findProduct(String name) {
20 return products.stream()
21 .filter(p -> p.equalsIgnoreCase(name))
22 .findFirst();
23 }
24
25 public List<String> getAllProducts() {
26 return List.copyOf(products); // defensive copy — immutable view
27 }
28
29 public int size() { return products.size(); }
30}1// File: com/differentteam/ShopFrontend.java
2package com.differentteam;
3
4import com.devstackflow.api.ProductCatalogue; // public class — importable
5
6public class ShopFrontend {
7
8 public static void main(String[] args) {
9
10 // public class + public constructor = instantiable from anywhere
11 ProductCatalogue catalogue = new ProductCatalogue();
12 catalogue.addProduct("Laptop");
13 catalogue.addProduct("Mouse");
14 catalogue.addProduct("Keyboard");
15
16 System.out.println("All products : " + catalogue.getAllProducts());
17 System.out.println("Found Laptop : " + catalogue.findProduct("laptop").orElse("not found"));
18 System.out.println("Size : " + catalogue.size());
19 }
20}Output:
All products : [Laptop, Mouse, Keyboard]
Found Laptop : Laptop
Size : 3
2 — public Methods
public methods form the callable API surface of a class. They represent operations that callers are expected to invoke. Everything else — helpers, validators, internal state management — should be private or less visible.
1// File: com/devstackflow/service/OrderService.java
2package com.devstackflow.service;
3
4import java.time.LocalDateTime;
5import java.util.HashMap;
6import java.util.Map;
7
8public class OrderService {
9
10 // private state — not accessible from outside
11 private final Map<String, Double> orders = new HashMap<>();
12 private int orderCounter = 1;
13
14 // public methods — the intentional API
15 public String createOrder(String customerId, double amount) {
16 validateInput(customerId, amount); // private helper
17 String orderId = generateOrderId(); // private helper
18 orders.put(orderId, amount);
19 System.out.println("Order created: " + orderId + " | Rs." + amount);
20 return orderId;
21 }
22
23 public boolean cancelOrder(String orderId) {
24 if (!orders.containsKey(orderId)) return false;
25 orders.remove(orderId);
26 System.out.println("Order cancelled: " + orderId);
27 return true;
28 }
29
30 public double getOrderAmount(String orderId) {
31 return orders.getOrDefault(orderId, -1.0);
32 }
33
34 public int getActiveOrderCount() { return orders.size(); }
35
36 // private helpers — implementation detail, not visible outside
37 private void validateInput(String customerId, double amount) {
38 if (customerId == null || customerId.isBlank())
39 throw new IllegalArgumentException("Customer ID required.");
40 if (amount <= 0)
41 throw new IllegalArgumentException("Amount must be positive.");
42 }
43
44 private String generateOrderId() {
45 return String.format("ORD-%04d", orderCounter++);
46 }
47}1// File: OrderServiceDemo.java
2package com.devstackflow.demo;
3
4import com.devstackflow.service.OrderService;
5
6public class OrderServiceDemo {
7
8 public static void main(String[] args) {
9
10 OrderService service = new OrderService();
11
12 String id1 = service.createOrder("CUST-101", 1299.50);
13 String id2 = service.createOrder("CUST-102", 4999.00);
14 String id3 = service.createOrder("CUST-103", 799.00);
15
16 System.out.println("Active orders: " + service.getActiveOrderCount());
17 System.out.println("ORD-0001 amt : Rs." + service.getOrderAmount(id1));
18
19 service.cancelOrder(id2);
20 System.out.println("After cancel : " + service.getActiveOrderCount());
21
22 // service.validateInput(...) ← compile error — private method
23 // service.generateOrderId() ← compile error — private method
24 // service.orders.put(...) ← compile error — private field
25 }
26}Output:
Order created: ORD-0001 | Rs.1299.5
Order created: ORD-0002 | Rs.4999.0
Order created: ORD-0003 | Rs.799.0
Active orders: 3
ORD-0001 amt : Rs.1299.5
Order cancelled: ORD-0002
After cancel : 2
3 — public static final Fields — Constants
The one case where public fields are appropriate: constants. A public static final field is immutable — its value can never change — so there is no risk of external code corrupting state through direct access.
1// File: com/devstackflow/config/AppConstants.java
2package com.devstackflow.config;
3
4public final class AppConstants {
5
6 // Prevent instantiation — this class is purely a constant holder
7 private AppConstants() { }
8
9 // public static final — constants safe for direct access
10 public static final String APP_NAME = "DevStackFlow";
11 public static final String APP_VERSION = "2.1.0";
12 public static final int MAX_RETRY_COUNT = 3;
13 public static final long SESSION_TIMEOUT = 30 * 60 * 1000L; // 30 minutes in ms
14 public static final double GST_RATE = 0.18;
15 public static final String BASE_CURRENCY = "INR";
16
17 // HTTP Status codes
18 public static final int HTTP_OK = 200;
19 public static final int HTTP_CREATED = 201;
20 public static final int HTTP_BAD_REQUEST = 400;
21 public static final int HTTP_UNAUTHORIZED = 401;
22 public static final int HTTP_NOT_FOUND = 404;
23 public static final int HTTP_SERVER_ERROR = 500;
24}1// File: ConstantsDemo.java
2package com.devstackflow.demo;
3
4import com.devstackflow.config.AppConstants;
5import static com.devstackflow.config.AppConstants.MAX_RETRY_COUNT;
6import static com.devstackflow.config.AppConstants.GST_RATE;
7
8public class ConstantsDemo {
9
10 public static void main(String[] args) {
11
12 // Accessed via class name — clear and unambiguous
13 System.out.println("App : " + AppConstants.APP_NAME
14 + " v" + AppConstants.APP_VERSION);
15 System.out.println("Session : " + AppConstants.SESSION_TIMEOUT / 60000 + " mins");
16 System.out.println("Base curr : " + AppConstants.BASE_CURRENCY);
17 System.out.println("HTTP OK : " + AppConstants.HTTP_OK);
18
19 // Accessed via static import — clean in math-heavy methods
20 double price = 2499.0;
21 double total = price + (price * GST_RATE);
22 System.out.printf("Price + GST: Rs.%.2f%n", total);
23
24 System.out.println("Max retries: " + MAX_RETRY_COUNT);
25
26 // AppConstants.APP_NAME = "Hacked"; ← compile error — final field
27 // new AppConstants(); ← compile error — private constructor
28 }
29}Output:
App : DevStackFlow v2.1.0
Session : 30 mins
Base curr : INR
HTTP OK : 200
Price + GST: Rs.2948.82
Max retries: 3
4 — public Constructors vs Restricted Constructors
public constructors allow unrestricted instantiation. Sometimes you want to control how objects are created — use private or protected constructors combined with static factory methods.
1// File: com/devstackflow/model/DatabaseConnection.java
2package com.devstackflow.model;
3
4public class DatabaseConnection {
5
6 private static DatabaseConnection instance; // singleton — only one instance ever
7
8 private final String host;
9 private final int port;
10 private boolean connected;
11
12 // private constructor — callers cannot use new DatabaseConnection(...)
13 private DatabaseConnection(String host, int port) {
14 this.host = host;
15 this.port = port;
16 this.connected = false;
17 }
18
19 // public static factory — the controlled way to get an instance
20 public static DatabaseConnection getInstance(String host, int port) {
21 if (instance == null) {
22 instance = new DatabaseConnection(host, port);
23 System.out.println("New connection created: " + host + ":" + port);
24 } else {
25 System.out.println("Returning existing connection.");
26 }
27 return instance;
28 }
29
30 // public methods — the API
31 public void connect() {
32 connected = true;
33 System.out.println("Connected to " + host + ":" + port);
34 }
35
36 public void disconnect() {
37 connected = false;
38 System.out.println("Disconnected from " + host + ":" + port);
39 }
40
41 public boolean isConnected() { return connected; }
42 public String getHost() { return host; }
43 public int getPort() { return port; }
44}1// File: DbConnectionDemo.java
2package com.devstackflow.demo;
3
4import com.devstackflow.model.DatabaseConnection;
5
6public class DbConnectionDemo {
7
8 public static void main(String[] args) {
9
10 // Cannot use new — private constructor
11 // DatabaseConnection conn = new DatabaseConnection("localhost", 5432); ← error
12
13 // Must use the public static factory method
14 DatabaseConnection conn1 = DatabaseConnection.getInstance("db.meesho.in", 5432);
15 conn1.connect();
16 System.out.println("Connected : " + conn1.isConnected());
17
18 // Second call — returns the same instance
19 DatabaseConnection conn2 = DatabaseConnection.getInstance("db.meesho.in", 5432);
20 System.out.println("Same instance: " + (conn1 == conn2)); // true — singleton
21
22 conn1.disconnect();
23 }
24}Output:
New connection created: db.meesho.in:5432
Connected to db.meesho.in:5432
Connected : true
Returning existing connection.
Same instance: true
Disconnected from db.meesho.in:5432
5 — public Methods in Interfaces
Every method declared in an interface is implicitly public and abstract (unless it is default or static). When a class implements an interface, its implementations must be public.
1// File: com/devstackflow/contract/Payable.java
2package com.devstackflow.contract;
3
4public interface Payable {
5
6 // implicitly public and abstract — no keyword needed
7 String initiatePayment(String orderId, double amount);
8 boolean verifyPayment(String transactionId);
9 boolean refundPayment(String transactionId, double amount);
10
11 // default method — has implementation, still implicitly public
12 default String getPaymentSummary(String orderId, double amount) {
13 return String.format("Payment for order %s: Rs.%.2f", orderId, amount);
14 }
15
16 // static method — utility, still implicitly public
17 static boolean isValidAmount(double amount) {
18 return amount > 0 && amount <= 100_000;
19 }
20}1// File: com/devstackflow/payment/UpiPayment.java
2package com.devstackflow.payment;
3
4import com.devstackflow.contract.Payable;
5
6public class UpiPayment implements Payable {
7
8 private final String upiId;
9
10 public UpiPayment(String upiId) {
11 this.upiId = upiId;
12 }
13
14 // Must be public — interface contract requires it
15 @Override
16 public String initiatePayment(String orderId, double amount) {
17 if (!Payable.isValidAmount(amount)) {
18 return "FAILED: invalid amount";
19 }
20 String txnId = "UPI-" + System.currentTimeMillis();
21 System.out.println("UPI payment initiated: " + txnId
22 + " | " + upiId + " | Rs." + amount);
23 return txnId;
24 }
25
26 @Override
27 public boolean verifyPayment(String transactionId) {
28 System.out.println("Verified: " + transactionId);
29 return transactionId.startsWith("UPI-");
30 }
31
32 @Override
33 public boolean refundPayment(String transactionId, double amount) {
34 System.out.println("Refund Rs." + amount + " for: " + transactionId);
35 return true;
36 }
37}1// File: PayableDemo.java
2package com.devstackflow.demo;
3
4import com.devstackflow.contract.Payable;
5import com.devstackflow.payment.UpiPayment;
6
7public class PayableDemo {
8
9 public static void main(String[] args) {
10
11 Payable payment = new UpiPayment("priya@meesho");
12
13 // Static interface method — called on the interface itself
14 System.out.println("Valid Rs.500 : " + Payable.isValidAmount(500.0));
15 System.out.println("Valid Rs.-1 : " + Payable.isValidAmount(-1.0));
16
17 System.out.println();
18
19 String txnId = payment.initiatePayment("ORD-001", 1299.0);
20 System.out.println("TxnId : " + txnId);
21 System.out.println("Verify: " + payment.verifyPayment(txnId));
22 System.out.println("Summary: " + payment.getPaymentSummary("ORD-001", 1299.0));
23
24 payment.refundPayment(txnId, 1299.0);
25 }
26}Output:
Valid Rs.500 : true
Valid Rs.-1 : false
UPI payment initiated: UPI-1705312200000 | priya@meesho | Rs.1299.0
TxnId : UPI-1705312200000
Verify: true
Summary: Payment for order ORD-001: Rs.1299.00
Refund Rs.1299.0 for: UPI-1705312200000
public — What to Expose vs What to Hide
Design rule — the public API should be: ✓ Stable — callers depend on it; changing it breaks them ✓ Minimal — expose only what callers genuinely need ✓ Safe — public methods should validate before acting on state ✓ Named intentionally — method names should describe WHAT, not HOW What should NOT be public: ✗ Implementation details (how something works internally) ✗ Mutable fields (callers could corrupt state) ✗ Internal helpers (generateId(), validateInput(), etc.) ✗ Partially-built state (methods that only make sense mid-construction) The iceberg principle: ┌─────────────────────────────────┐ │ PUBLIC API SURFACE │ ← small, stable, intentional │ createOrder() cancelOrder() │ │ getStatus() getTotal() │ ├─────────────────────────────────┤ │ (below the waterline) │ │ private fields │ ← large, hidden, free to change │ private helpers │ │ default package utilities │ │ protected extension hooks │ └─────────────────────────────────┘
Real-World Example — Product Search API
The Business Problem
A product search service at a company like Flipkart exposes a public API that external teams — the web frontend, the mobile app, and the recommendation engine — can call. The internal implementation — scoring logic, cache management, query building — is private. This separation lets the team change how search works internally without breaking any caller.
1// File: com/flipkart/search/model/SearchResult.java
2package com.flipkart.search.model;
3
4public class SearchResult {
5
6 private final String productId;
7 private final String name;
8 private final String category;
9 private final double price;
10 private final double relevanceScore;
11 private final int stockCount;
12
13 public SearchResult(String productId, String name,
14 String category, double price,
15 double relevanceScore, int stockCount) {
16 this.productId = productId;
17 this.name = name;
18 this.category = category;
19 this.price = price;
20 this.relevanceScore = relevanceScore;
21 this.stockCount = stockCount;
22 }
23
24 public String getProductId() { return productId; }
25 public String getName() { return name; }
26 public String getCategory() { return category; }
27 public double getPrice() { return price; }
28 public double getRelevanceScore(){ return relevanceScore; }
29 public boolean isInStock() { return stockCount > 0; }
30
31 @Override
32 public String toString() {
33 return String.format("[%s] %-30s Rs.%7.2f | %s | score=%.2f",
34 productId, name, price,
35 isInStock() ? "IN STOCK" : "OUT OF STOCK",
36 relevanceScore);
37 }
38}1// File: com/flipkart/search/SearchService.java
2package com.flipkart.search;
3
4import com.flipkart.search.model.SearchResult;
5
6import java.util.ArrayList;
7import java.util.Comparator;
8import java.util.List;
9import java.util.stream.Collectors;
10
11public class SearchService {
12
13 // private — internal product database (would be a real DB in production)
14 private final List<SearchResult> productIndex = new ArrayList<>();
15
16 // public constructor — external teams instantiate this service
17 public SearchService() {
18 loadSampleData(); // private initialisation
19 }
20
21 // ── PUBLIC API ────────────────────────────────────────────────────
22
23 // Search by keyword — external teams call this
24 public List<SearchResult> search(String keyword) {
25 if (keyword == null || keyword.isBlank()) return List.of();
26 String lower = keyword.toLowerCase();
27 return productIndex.stream()
28 .filter(p -> matchesKeyword(p, lower)) // private helper
29 .sorted(Comparator.comparingDouble(
30 SearchResult::getRelevanceScore).reversed())
31 .collect(Collectors.toList());
32 }
33
34 // Filter by category — external teams call this
35 public List<SearchResult> searchByCategory(String category) {
36 if (category == null || category.isBlank()) return List.of();
37 return productIndex.stream()
38 .filter(p -> p.getCategory().equalsIgnoreCase(category))
39 .filter(SearchResult::isInStock)
40 .sorted(Comparator.comparingDouble(SearchResult::getPrice))
41 .collect(Collectors.toList());
42 }
43
44 // Price-range filter — external teams call this
45 public List<SearchResult> searchByPriceRange(double min, double max) {
46 validatePriceRange(min, max); // private validator
47 return productIndex.stream()
48 .filter(p -> p.getPrice() >= min && p.getPrice() <= max)
49 .filter(SearchResult::isInStock)
50 .sorted(Comparator.comparingDouble(SearchResult::getPrice))
51 .collect(Collectors.toList());
52 }
53
54 // Total indexed products — external teams call this
55 public int getTotalIndexed() { return productIndex.size(); }
56
57 // ── PRIVATE IMPLEMENTATION ────────────────────────────────────────
58
59 private boolean matchesKeyword(SearchResult product, String keyword) {
60 return product.getName().toLowerCase().contains(keyword)
61 || product.getCategory().toLowerCase().contains(keyword);
62 }
63
64 private void validatePriceRange(double min, double max) {
65 if (min < 0) throw new IllegalArgumentException("Min price cannot be negative.");
66 if (max < min) throw new IllegalArgumentException("Max must be >= min.");
67 }
68
69 private double computeScore(String name, String keyword) {
70 // Simplified scoring — real systems use TF-IDF or BM25
71 if (name.toLowerCase().startsWith(keyword)) return 1.0;
72 if (name.toLowerCase().contains(keyword)) return 0.7;
73 return 0.4;
74 }
75
76 private void loadSampleData() {
77 productIndex.add(new SearchResult("P001", "Wireless Bluetooth Headphones",
78 "Electronics", 2499.0, computeScore("Wireless Bluetooth Headphones", "headphone"), 120));
79 productIndex.add(new SearchResult("P002", "Laptop Stand Adjustable Aluminium",
80 "Accessories", 1299.0, computeScore("Laptop Stand Adjustable Aluminium", "laptop"), 80));
81 productIndex.add(new SearchResult("P003", "Mechanical Gaming Keyboard RGB",
82 "Electronics", 3999.0, computeScore("Mechanical Gaming Keyboard RGB", "keyboard"), 60));
83 productIndex.add(new SearchResult("P004", "Laptop Bag 15.6 inch Waterproof",
84 "Accessories", 899.0, computeScore("Laptop Bag 15.6 inch Waterproof", "laptop"), 0));
85 productIndex.add(new SearchResult("P005", "USB-C Hub 7-in-1 Multiport",
86 "Electronics", 1899.0, computeScore("USB-C Hub 7-in-1 Multiport", "usb"), 200));
87 productIndex.add(new SearchResult("P006", "Noise Cancelling Headphones Pro",
88 "Electronics", 8999.0, computeScore("Noise Cancelling Headphones Pro", "headphone"), 45));
89 productIndex.add(new SearchResult("P007", "Laptop Cooling Pad with Fan",
90 "Accessories", 699.0, computeScore("Laptop Cooling Pad with Fan", "laptop"), 150));
91 }
92}1// File: com/flipkart/SearchApiDemo.java
2package com.flipkart;
3
4import com.flipkart.search.SearchService;
5import com.flipkart.search.model.SearchResult;
6
7import java.util.List;
8
9public class SearchApiDemo {
10
11 public static void main(String[] args) {
12
13 System.out.println("╔══════════════════════════════════════════╗");
14 System.out.println("║ FLIPKART PRODUCT SEARCH SERVICE DEMO ║");
15 System.out.println("╚══════════════════════════════════════════╝\n");
16
17 // public constructor — external team creates service
18 SearchService searchService = new SearchService();
19 System.out.println("Products indexed: " + searchService.getTotalIndexed());
20
21 System.out.println("\n=== Search: 'headphone' ===");
22 List<SearchResult> results = searchService.search("headphone");
23 results.forEach(r -> System.out.println(" " + r));
24
25 System.out.println("\n=== Category: 'Accessories' (in stock, sorted by price) ===");
26 searchService.searchByCategory("Accessories")
27 .forEach(r -> System.out.println(" " + r));
28
29 System.out.println("\n=== Price Range: Rs.1000 – Rs.3000 ===");
30 searchService.searchByPriceRange(1000, 3000)
31 .forEach(r -> System.out.println(" " + r));
32
33 // External teams cannot access private implementation:
34 // searchService.loadSampleData(); ← compile error
35 // searchService.computeScore(...) ← compile error
36 // searchService.productIndex.add(...) ← compile error
37 // searchService.validatePriceRange(...)← compile error
38 }
39}Output:
╔══════════════════════════════════════════╗
║ FLIPKART PRODUCT SEARCH SERVICE DEMO ║
╚══════════════════════════════════════════╝
Products indexed: 7
=== Search: 'headphone' ===
[P001] Wireless Bluetooth Headphones Rs. 2499.00 | IN STOCK | score=0.70
[P006] Noise Cancelling Headphones Pro Rs. 8999.00 | IN STOCK | score=0.70
=== Category: 'Accessories' (in stock, sorted by price) ===
[P007] Laptop Cooling Pad with Fan Rs. 699.00 | IN STOCK | score=0.40
[P002] Laptop Stand Adjustable Aluminium Rs. 1299.00 | IN STOCK | score=0.40
=== Price Range: Rs.1000 – Rs.3000 ===
[P002] Laptop Stand Adjustable Aluminium Rs. 1299.00 | IN STOCK | score=0.40
[P005] USB-C Hub 7-in-1 Multiport Rs. 1899.00 | IN STOCK | score=0.40
[P001] Wireless Bluetooth Headphones Rs. 2499.00 | IN STOCK | score=0.70
Five public methods form the entire API: search(), searchByCategory(), searchByPriceRange(), getTotalIndexed(), and the constructor. Five private methods handle the implementation: matchesKeyword(), validatePriceRange(), computeScore(), loadSampleData(). External teams can never see or depend on the implementation — only the four public methods. The team can swap the internal algorithm completely without breaking a single caller.
Best Practices
Design the public API before writing the implementation. Write the public method signatures first — name them clearly, define their parameters and return types. Then implement them privately. This API-first approach produces stable, well-named public surfaces and forces separation between contract and implementation from the start.
Keep the public surface as small as possible. Every public method you add is a commitment — callers will depend on it, and changing or removing it later requires updating every caller. A method that is public today but should be private can be made private if no callers exist yet. Once callers exist, changing visibility is a breaking change. Err towards narrower visibility and widen when genuinely needed.
Never make mutable instance fields public. public double price lets any caller write product.price = -1000 without any validation. Always make instance fields private and provide controlled access through getter/setter methods that enforce business rules. The only safe public fields are static final constants — immutable, so direct access cannot corrupt state.
Document every public method with its contract. public methods are promises to callers. Document what the method expects (preconditions), what it guarantees (postconditions), what exceptions it throws, and whether it is thread-safe. Private methods are implementation details that only you maintain — they need less formal documentation.
Common Mistakes
Mistake 1 — Accidental public on Internal Helpers
1// WRONG — generateId is an implementation detail
2public class OrderService {
3 public String generateId() { // accidental public — now callers depend on this
4 return "ORD-" + System.currentTimeMillis();
5 }
6 public String createOrder(...) {
7 String id = generateId(); // callers now bypass createOrder and call this directly
8 ...
9 }
10}
11
12// CORRECT — generateId is private — callers use createOrder only
13public class OrderService {
14 public String createOrder(...) { ... } // only this is public
15 private String generateId() { ... } // hidden helper
16}Mistake 2 — public Mutable Fields
1// WRONG — any caller can corrupt state
2public class Product {
3 public double price; // product.price = -999 is valid — no validation
4 public int stockCount; // product.stockCount = -50 is valid — wrong
5}
6
7// CORRECT — all state changes go through validated methods
8public class Product {
9 private double price;
10 private int stockCount;
11
12 public void setPrice(double price) {
13 if (price < 0) throw new IllegalArgumentException("Price cannot be negative.");
14 this.price = price;
15 }
16 public double getPrice() { return price; }
17}Mistake 3 — Two public Classes in One File
1// WRONG — file is named OrderService.java
2public class OrderService { } // matches filename — OK
3public class OrderHelper { } // compile error: class OrderHelper is public,
4 // should be declared in a file named OrderHelper.java
5// Only ONE public class per .java file
6// The file name must match the public class name
7
8// CORRECT — move OrderHelper to its own file, or make it package-private
9class OrderHelper { } // no public — same file is fineMistake 4 — Implementing Interface Method as Non-public
1interface Printable {
2 void print(); // implicitly public
3}
4
5class Document implements Printable {
6 // @Override
7 // void print() { } // compile error — cannot reduce to package-private
8 // // interface method is public; impl must be public
9
10 @Override
11 public void print() { // must be public
12 System.out.println("Printing...");
13 }
14}Interview Questions
Q1. What does the public access modifier mean in Java?
public is the least restrictive access modifier. A public class, method, field, or constructor is accessible from any class in any package in the entire program. There are no package, inheritance, or proximity restrictions — any code that can see the class can call its public members. On top-level classes, public means the class is importable from any package. On members, public means any caller that holds an instance or knows the class name can access the member directly.
Q2. What is the difference between a public method and a public static method?
A public instance method requires an object instance to be called — obj.method(). It has access to this and can operate on the object's fields. A public static method belongs to the class itself — ClassName.method() — and does not need an instance. It cannot access instance fields or this. Static methods are used for utilities, factory methods, and operations that don't depend on object state. Math.max(), String.valueOf(), and Arrays.sort() are all public static methods.
Q3. Can a public method be overridden in a subclass?
Yes. A public method can be overridden by any subclass — in the same package or in a different package. The overriding method must also be public (or remain public — it cannot be narrowed). When overriding, the @Override annotation makes the intent explicit and causes a compile error if the method signature does not actually match any parent method. Polymorphism uses the overridden method when the subclass instance is accessed through a parent-type reference.
Q4. Why are all interface methods implicitly public?
An interface defines a contract that implementing classes must fulfil for all callers. If interface methods could be private or default, callers would not know which methods they could actually invoke — the contract would be inconsistent. Making all abstract interface methods implicitly public ensures that every implementation class exposes the full interface contract to all callers, maintaining the fundamental premise of interface-based programming.
Q5. Is it good practice to have public fields in Java?
For mutable instance fields — no. A public instance field bypasses encapsulation: any caller can read and write it without restriction, making validation impossible, breaking invariants, and coupling external code to the internal representation. Changing the field name or type later becomes a breaking change for all callers. The only appropriate use of public fields is public static final constants — which are immutable and safe for direct access.
Q6. What happens when a top-level class does not have public access?
A top-level class without public gets default (package-private) access. It is only visible within its own package — it cannot be imported by classes in other packages, and those other-package classes cannot use it at all. This is appropriate for internal helper classes that are only needed within a single package. One file can have only one public top-level class, and the file name must match that class's name exactly.
FAQs
Can you have a public method inside a private nested class?
Yes. A public method inside a private nested class is technically public relative to the nested class, but the enclosing constraint wins — since the nested class itself is private, only code inside the enclosing outer class can access the nested class and therefore its methods. The public on the method has no practical effect from outside the outer class.
Can you call a public method without creating an object?
Only if the method is also static. public static methods are called on the class: ClassName.method(). A public instance method always requires an object reference: obj.method(). Attempting to call an instance method without an object — ClassName.instanceMethod() — causes a compile error.
Does making a method public affect performance?
No. The public modifier has no effect on bytecode or runtime performance. The JVM does not execute any visibility check at runtime for public members — the access control check was already performed and resolved by the compiler. public, private, protected, and default methods all execute at identical speed.
Can you have a public class in the default (unnamed) package?
Yes, but it creates a problem. A public class in the default package cannot be imported by named-package classes — the import statement requires a package prefix that the default package does not have. So while the class is technically public, it is only usable within the same default-package context. This is another reason to avoid the default package in production code.
What is the purpose of a public constructor versus a public static factory method?
A public constructor is the standard way to create instances — new ClassName(args). A public static factory method — like getInstance(), of(), or create() — controls how and whether new instances are created. Factory methods can enforce singleton patterns, return subtype instances, return cached instances, validate parameters before creation, and have meaningful names that new cannot express. Optional.of(), List.of(), and LocalDate.of() are all factory methods.
Summary
public is the declaration that says: this is part of the outward-facing contract. It grants unrestricted access — any class, any package, any context. Used on a class it means importable everywhere. Used on a method it means callable by anyone. Used on a constant field (static final) it means readable by anyone without risk.
The discipline of public is minimal exposure. Every public method is a commitment that callers will depend on. Every unnecessary public member is a future maintenance liability. The right practice: start with private, expose through public only what external callers genuinely need, and document every public method as a stable contract.
What to Read Next
Learn how the private modifier restricts access to a class only.