Java Tutorial
🔍

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 fine

Mistake 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