Interface: Essence, Contracts in Java and Kotlin for Android

Author: IT Sectr Published: 2026-02-18 Reading time: 9 min

Interface is a contract defining a set of abstract methods that a class must implement. In Java and Kotlin, interfaces are the primary mechanism for abstraction and polymorphism. In Java 8+, interfaces can contain default and static methods, in Kotlin — default implementations. According to Google Android Developers (2025), interfaces are used in 90% of Android projects to define architecture layers — repositories, UseCases, and services.

Key Takeaways

  • Interface is an abstract type defining method signatures without implementation (prior to Java 8)
  • Implements is the keyword linking a class to an interface; a class can implement multiple interfaces
  • Default method is a method with implementation in a Java interface, added in Java 8 for backward compatibility
  • Kotlin interface supports properties with getters and method implementations, replacing abstract class in many scenarios
  • Markup interface is an empty interface used as a marker (Serializable, Cloneable, RandomAccess)

What is Interface in Java and Kotlin?

Interface is a reference type that contains abstract methods, constants, and default methods. A class implementing an interface must provide implementations for all its abstract methods. In Java, an interface cannot have state (instance fields); Kotlin also adheres to this restriction but supports properties with accessors.

Java
public interface Repository {
    T findById(Long id);
    List findAll();
    T save(T entity);
    void deleteById(Long id);
    
    default long count() {
        return findAll().size();
    }
}

@Entity
public class UserEntity {
    private Long id;
    private String email;
    // getters and setters
}

public class UserRepositoryImpl implements Repository {
    private final EntityManager em;
    
    public UserEntity findById(Long id) {
        return em.find(UserEntity.class, id);
    }
    
    public List findAll() {
        return em.createQuery("FROM UserEntity", UserEntity.class)
                .getResultList();
    }
    // other methods
}

Repository<T> is a generic interface with CRUD methods. The default method count() provides a default implementation that can be overridden. UserRepositoryImpl implements the interface using EntityManager for data access. This approach allows testing the data layer through an interface mock without a real database.

Interface vs Abstract Class: When to Choose What

The choice between an interface and an abstract class depends on the presence of shared state and the relationships between types. An interface defines a contract (what a class can do), an abstract class defines a common implementation (what a class is).

CriteriaInterfaceAbstract Class
State (fields)Only static final constantsYes, any fields
ConstructorsNoYes
Multiple inheritanceYes (implements)No (extends one)
Access modifierspublic (Java 8-), default methodsAll (private, protected, public)
When to useContract for unrelated classesCommon base for related classes

In Clean Architecture, interfaces are placed in the inner layer (domain), and implementations in the outer layer (data). This allows maintaining the Dependency Rule: the outer layer depends on the inner layer, but not vice versa. Abstract classes are more often used for template methods (Template Method pattern).

Interface in Kotlin: Properties and Delegates

Kotlin interfaces are more flexible than Java: they can declare abstract properties and provide method implementations. Unlike Java, Kotlin supports delegation via the by keyword, which powerfully reduces boilerplate when implementing the Delegate pattern.

Kotlin
interface ApiService {
    val baseUrl: String  // abstract property
    
    suspend fun fetchData(): Result>
    
    fun getEndpoint(path: String): String {
        return "$baseUrl/$path"  // default implementation
    }
}

class RetrofitApiService(
    override val baseUrl: String
) : ApiService {
    private val client = Retrofit.Builder()
        .baseUrl(baseUrl)
        .build()
    
    override suspend fun fetchData(): Result> {
        // request implementation
    }
}

// Delegation via by
interface Logger {
    fun log(message: String)
}

class ConsoleLogger : Logger {
    override fun log(message: String) = println(message)
}

class UserService(logger: Logger) : Logger by logger

UserService implements the Logger interface through delegation (by). All log() calls are forwarded to the logger object without writing wrapper methods. This is an example of composition that in Java would require 5-10 lines of boilerplate code.

Interface in Clean Architecture Android

Clean Architecture for Android clearly separates the application into layers. Interfaces act as boundaries between layers: Domain defines repository and UseCase interfaces, Data provides implementations. This allows replacing implementations without changing business logic — a key advantage when migrating from Room to Firebase or from REST to GraphQL.

Kotlin
// Domain layer — interface (contract)
interface UserRepository {
    suspend fun getUser(id: String): User
    suspend fun updateUser(user: User)
}

// Domain layer — use case (depends on interface)
class GetUserUseCase(
    private val repository: UserRepository
) {
    suspend operator fun invoke(id: String): Result {
        return runCatching { repository.getUser(id) }
    }
}

// Data layer — implementation
class UserRepositoryImpl(
    private val api: UserApi,
    private val dao: UserDao
) : UserRepository {
    override suspend fun getUser(id: String): User {
        val cached = dao.getUser(id)
        if (cached != null) return cached
        val remote = api.fetchUser(id)
        dao.insertUser(remote)
        return remote
    }
    // updateUser...
}

UserRepository is an interface in the domain layer. GetUserUseCase depends on the interface, not the implementation. UserRepositoryImpl in the data layer implements the interface, combining API and local database. This architecture allows testing UseCases with a mock repository without setting up a database or network.

Default and Static Methods in Java 8+

Default methods were added in Java 8 for evolutionary extension of interfaces without breaking backward compatibility. If ArrayList did not implement the new stream() method added to Collection, old code would continue to work. Static methods in interfaces serve as utilities related to the interface — an alternative to utility classes.

Java
public interface Vehicle {
    void start();
    void stop();
    
    default void honk() {
        System.out.println("Beep!");
    }
    
    static Vehicle of(String type) {
        if ("car".equals(type)) return new Car();
        return new Bicycle();
    }
    
    // constant
    String CATEGORY = "transport";
}

Default methods solve the diamond problem: if a class implements two interfaces with the same default method, the compiler requires explicit overriding. Static methods are called through the interface name — Vehicle.of("car"), without an instance.

Common Mistakes in Interface Design

Mistakes in interface design lead to fragile code, testing complexity, and SOLID violations. Let's look at three common problems.

Interface Pollution — Too Many Methods in One Interface

An interface containing 15+ methods violates the Interface Segregation Principle (ISP). An example is the old java.util.Dictionary with 10+ methods. Solution: split into several small interfaces — ReadableRepository, WritableRepository, SearchableRepository. A client (service) depends only on the methods it needs.

Excessive Abstraction — Interface for Every Class

Creating an interface for every class without a real need for polymorphism is the Interface overkill antipattern. Indicator: the interface has exactly one implementation, and the project has no plans to add alternatives. Solution: add an interface only when a second implementation option appears or a mock is needed for testing.

Frequently Asked Questions

How does interface differ from abstract class in Java?

Interface defines only a contract (method signatures), cannot have state, and supports multiple inheritance. Abstract class can contain fields, constructors, and implemented methods, but a class can inherit only one abstract class. Since Java 8, interfaces have gained default and static methods, narrowing the gap.

Can an interface inherit another interface?

Yes, in Java and Kotlin interfaces support inheritance. public interface AdvancedRepository<T> extends Repository<T>, Pageable is an interface combining two others. A class implementing AdvancedRepository must implement all methods of both parent interfaces. Multiple inheritance is allowed only for interfaces.

What is a functional interface in Java?

Functional interface is an interface with a single abstract method (SAM — Single Abstract Method). The @FunctionalInterface annotation guarantees this restriction. Examples: Runnable, Callable, Comparator, Consumer. Functional interfaces are the foundation of Java 8 lambda expressions: () -> System.out.println() implements Runnable.

Why are default methods needed in Java interfaces?

Default methods allow adding new methods to an interface without modifying all implementing classes. For example, Java 8 added stream() to Collection as a default method. Without this mechanism, foreach(), stream(), and other methods would require changes to thousands of classes in the JDK. Default is a backward-compatible way of extension.

How does Kotlin solve the multiple inheritance problem?

Kotlin prohibits multiple class inheritance but allows multiple interface implementation. If two interfaces have a method with the same signature and default implementation, the compiler requires explicit overriding with super<InterfaceName>.method() call. This resolves the diamond problem at the compilation level.

Summary

  • Interface is a contract defining methods that a class must implement; the primary mechanism for polymorphism in Java and Kotlin
  • Java 8+ added default and static methods to interfaces, narrowing the gap with abstract classes
  • Kotlin interfaces support abstract properties, default implementations, and delegation via by
  • Clean Architecture uses interfaces as boundaries between Domain and Data layers
  • Interface Segregation Principle requires splitting large interfaces into specialized ones
  • Functional interfaces (Single Abstract Method) are the foundation of lambda expressions and Stream API
  • Recommendation: add an interface when a second implementation appears or a mock is needed for testing

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also