Forberedelse til Java backend-interview

Java-interviewspørgsmål og svar 2026

Et sæt med 30 Java-interviewspørgsmål til backendudviklere, fra grundlæggende sprogmekanismer og JVM-adfærd til collections, Stream API og samtidighed.

Start Java-prøvesamtaleIntet kreditkort kræves. 1 gratis session er tilgængelig.
Øv tekniske jobsamtaler på engelskEn tilstand, hvor ikke-modersmålstalende kan øve sig i at bestå tekniske interviews.

Java og JVM

1Hvad er Java, og hvorfor betragtes det som platformsuafhængigt?

Java er et statisk typet programmeringssprog til generelle formål. Kildekoden kompileres til bytecode, og bytecode afvikles på en JVM (Java Virtual Machine). Den samme .class- eller .jar-fil kan normalt køre på forskellige operativsystemer uden omkompilering, forudsat at en kompatibel JVM er tilgængelig. Dette er dog ikke en absolut uafhængighed: native biblioteker, platformsspecifikke stier, systemkommandoer og operativsystemets adfærd kan stadig gøre en applikation platformsafhængig.

Java source -> javac -> bytecode -> JVM -> machine code
Prøv at besvare dette spørgsmål med en AI-coach

2Hvad er forskellen mellem JVM (Java Virtual Machine), JRE (Java Runtime Environment) og JDK (Java Development Kit)?

JVM'en indlæser og udfører bytekode og håndterer hukommelse, tråde og garbage collection. JRE refererede historisk set til JVM'en plus de biblioteker, der er nødvendige for at køre applikationer. JDK inkluderer kørselsmiljøet samt udviklingsværktøjer som `javac`, `java`, debugger, `javadoc`, `jar` og diagnostiske værktøjer. I moderne distributioner er en separat JRE mindre almindelig, fordi applikationer ofte leveres med et skræddersyet kørselsmiljø-image (runtime image).

JDK = development tools + runtime
Runtime = JVM + standard libraries
Prøv at besvare dette spørgsmål med en AI-coach

3Hvordan udfører JVM'en (Java Virtual Machine) Java-kode, og hvorfor er JIT-kompilering (Just-In-Time) nødvendig?

`javac` kompilerer kildekoden til bytecode. JVM'en indlæser klasser og begynder at fortolke eller udføre bytecode-instruktionerne. Hyppigt udførte kodeområder opdages ved kørsel og kompileres af JIT-kompileren til optimeret maskinkode. JIT kan inline metoder, optimere løkker, anvende escape-analyse og fjerne visse hukommelsestildelinger. Dette er grunden til, at Java-applikationer kan blive hurtigere efter opvarmning, og hvorfor mikrobenchmarks uden JMH (Java Microbenchmark Harness) ofte er misvisende.

Prøv at besvare dette spørgsmål med en AI-coach

4Hvordan fungerer `ClassLoader` i Java?

En `ClassLoader` indlæser klassedefinitioner i vores JVM (Java Virtual Machine). De almindelige indlæsere er Bootstrap, Platform, Application samt brugerdefinerede ("custom") indlæsere. En klasse identificeres ikke kun ved sit fuldt kvalificerede navn, men også af den `ClassLoader`, der indlæste den, så to klasser med samme navn fra forskellige indlæsere kan være forskellige typer. Java bruger normalt forældredelegering ("parent delegation"): en indlæser spørger først sin forælder for at reducere risikoen for at erstatte platformsklasser.

Loading
Linking: verification, preparation, resolution
Initialization
Prøv at besvare dette spørgsmål med en AI-coach

5Hvordan er hukommelsen i JVM (Java Virtual Machine) organiseret?

Nøgleområder i JVM-hukommelsen omfatter heapen til objekter og arrays, Java-stakke med kaldsrammer for hver tråd, Metaspace til klasse-metadata, code cache til JIT-kompileret (Just-In-Time) kode, PC-registret (Program Counter) og native-stakke. Undgå den forsimplede antagelse af, at primitive typer altid ligger på stakken, og objekter altid ligger på heapen: et primitivt felt gemmes internt i dets objekt, og JIT escape-analyse kan fjerne allokeringer eller ændre repræsentationen af objekter.

Prøv at besvare dette spørgsmål med en AI-coach

6Hvordan fungerer en GC (Garbage Collector)?

En GC (Garbage Collector) frigør hukommelse, der optages af objekter, som ikke længere kan nås fra GC-rødder (GC Roots). Rødderne inkluderer lokale variabler i aktive metoder, statiske felter, aktive tråde og JNI-referencer. Mange garbage collectors er baseret på observationen af, at de fleste nye objekter har en kort levetid, og derfor er heapen ofte opdelt i unge og gamle generationer. En GC lukker ikke filer eller forbindelser deterministisk; eksterne ressourcer bør lukkes med `try-with-resources`.

try (var stream = Files.newInputStream(path)) {
    // use stream
}
Prøv at besvare dette spørgsmål med en AI-coach

OOP og typer

7Hvilke fire principper for OOP (Object-Oriented Programming) understøtter Java?

Java understøtter indkapsling, abstraktion, arv og polymorfi. Indkapsling skjuler den interne tilstand bag et offentligt API. Abstraktion fokuserer på essentiel adfærd. Arv skaber en ny type baseret på en eksisterende type. Polymorfi lader kode arbejde med forskellige implementeringer gennem en fælles kontrakt. I praksis foretrækkes sammensætning ofte frem for arv, da det skaber lavere kobling.

interface PaymentService {
    void pay(BigDecimal amount);
}

class CardPaymentService implements PaymentService {
    @Override
    public void pay(BigDecimal amount) {
        // payment implementation
    }
}
Prøv at besvare dette spørgsmål med en AI-coach

8Hvad er forskellen på overloading og overriding af metoder?

Overloading betyder, at flere metoder har samme navn, men forskellige parametre; compileren vælger metoden baseret på de statiske argumenttyper. Overriding betyder, at en underklasse erstatter implementeringen af en instansmetode fra en forældreklasse; ved kørselstid vælges metoden baseret på objektets faktiske type. Statiske metoder skjules, de overskrives (overrides) ikke, og private metoder overskrives ikke, fordi de ikke nedarves som tilgængelige medlemmer.

void send(String message) {}
void send(String message, int priority) {}

class Animal {
    void speak() { System.out.println("Animal"); }
}

class Dog extends Animal {
    @Override
    void speak() { System.out.println("Dog"); }
}
Prøv at besvare dette spørgsmål med en AI-coach

9Hvad er forskellen mellem et interface og en abstrakt klasse?

Et interface definerer en kontrakt og kan indeholde abstrakte metoder, `default`-metoder, statiske metoder, konstanter og private hjælpemetoder. En abstrakt klasse kan derudover have instanstilstand, konstruktører, `protected`-felter, delt implementering samt abstrakte eller konkrete metoder. En klasse kan kun nedarve fra én klasse, men kan implementere flere interfaces. Brug et interface til en uafhængig kontrakt, og en abstrakt klasse til tæt relaterede typer med delt implementering og tilstand.

interface Repository<T> {
    Optional<T> findById(long id);
}
Prøv at besvare dette spørgsmål med en AI-coach

10Hvad betyder nøgleordet final?

En final-variabel kan kun tildeles en værdi én gang, men en final-reference gør ikke det refererede objekt uforanderligt. En final-metode kan ikke overskrives. En final-klasse kan ikke nedarves fra. Nøgleordet final formidler hensigt og begrænser ændringer af referencer eller arv, men i sig selv giver det hverken en fuldstændig uforanderlig model eller trådsikkerhed.

final int maxAttempts = 3;

final List<String> names = new ArrayList<>();
names.add("Alice"); // allowed

public final void validate() {}

public final class Token {}
Prøv at besvare dette spørgsmål med en AI-coach

11Hvad er records i Java?

En `record` er en kompakt syntaks for uforanderlige databærere. Kompileren genererer private `final`-felter, konstruktor, adgangsmetoder (accessors), `equals`, `hashCode` og `toString`. Records er nyttige til DTO'er (Data Transfer Objects), værdiobjekter og metoderesultater. De er ikke en erstatning for alle domæneentiteter: validering kan tilføjes i en kompakt konstruktor, men kompleks foranderlig adfærd, doven indlæsning (lazy loading) og nedarvningsbaserede modeller passer normalt dårligt til records.

public record User(long id, String name) {}

public record Order(List<String> items) {
    public Order {
        items = List.copyOf(items);
    }
}
Prøv at besvare dette spørgsmål med en AI-coach

12Hvordan opretter man et uforanderligt objekt i Java?

Gør klassen `final`, eller begræns arv omhyggeligt, gør felter `private final`, undgå setters og udfør validering i konstruktøren. For foranderlige input bør der oprettes defensive kopier, og der bør returneres kopier eller uforanderlige visninger (unmodifiable views). Dyb uforanderlighed kræver, at al indlejret tilstand også er uforanderlig. `final`-felter hjælper med sikker publicering, men et objekt kan stadig være logisk foranderligt, hvis det eksponerer en foranderlig samling eller et foranderligt indlejret objekt.

public final class User {
    private final String name;
    private final List<String> roles;

    public User(String name, List<String> roles) {
        this.name = name;
        this.roles = List.copyOf(roles);
    }

    public List<String> getRoles() {
        return roles;
    }
}
Prøv at besvare dette spørgsmål med en AI-coach

Referencer, strenge og sammenligning

13Overfører Java parametre som værdi (pass-by-value) eller som reference (pass-by-reference)?

Java overfører altid argumenter som værdi. For primitive typer kopieres selve værdien. For objekter kopieres værdien af referencen. En metode kan ændre (mutere) objektet gennem den kopierede reference, men den kan ikke erstatte kalderens reference med et andet objekt. Denne forskel forklarer, hvorfor ændring af et felt kan være synligt for kalderen, mens tildeling af parameteren til et nyt objekt ikke er det.

void change(int value) {
    value = 100;
}

void rename(User user) {
    user.setName("Kate");
}

void replace(User user) {
    user = new User("New");
}
Prøv at besvare dette spørgsmål med en AI-coach

14Hvad er forskellen mellem `==` og `equals()`?

For primitive typer sammenligner `==` værdier. For objektreferencer kontrollerer `==`, om begge referencer peger på det samme objekt. `equals()` er en metode, der bør sammenligne logisk lighed, hvis klassen implementerer den korrekt. `String`, `Integer`, `LocalDate` og mange værdiklasser overskriver `equals`. For brugerdefinerede domæneobjekter skal `equals` afspejle den tilsigtede identitet eller værdisemantik.

int a = 10;
int b = 10;
System.out.println(a == b); // true

String first = new String("java");
String second = new String("java");

System.out.println(first == second);      // false
System.out.println(first.equals(second)); // true
Prøv at besvare dette spørgsmål med en AI-coach

15Hvad er equals/hashCode-kontrakten?

Hvis to objekter er ens ifølge equals, skal de returnere den samme hashCode. Det omvendte er ikke et krav: forskellige objekter kan godt have den samme hashCode. equals bør være refleksiv, symmetrisk, transitiv, konsistent og returnere false for null. Hvis kontrakten brydes, ødelægger det adfærden i HashMap, HashSet og andre hash-baserede samlinger: et objekt kan blive umuligt at finde, selvom det er til stede.

@Override
public boolean equals(Object object) {
    if (this == object) return true;
    if (!(object instanceof User other)) return false;
    return id == other.id;
}

@Override
public int hashCode() {
    return Long.hashCode(id);
}
Prøv at besvare dette spørgsmål med en AI-coach

16Hvorfor er String uforanderlig?

String er uforanderlig af hensyn til sikkerhed, forudsigelighed og ydeevne. Uforanderlighed muliggør streng-internering, sikker deling mellem tråde, pålidelig caching af hashCode samt sikker brug i stier, URL'er, klassenavne og sikkerhedsfølsomme værdier. Operationer som concat, replace og substring opretter nye strenge. Siden Java 9 kan String internt bruge byte[] med en kompakt repræsentation, men den offentlige kontrakt om uforanderlighed forbliver den samme.

String value = "Java";
value.toUpperCase();

System.out.println(value); // Java

String a = "java";
String b = "java";

System.out.println(a == b); // often true due to interning
Prøv at besvare dette spørgsmål med en AI-coach

17Hvad er forskellen mellem `String`, `StringBuilder` og `StringBuffer`?

`String` er uforanderlig, så gentagen sammensætning i løkker kan skabe mange mellemliggende objekter, selvom compileren kan optimere simple tilfælde. `StringBuilder` er foranderlig og foretrækkes til at opbygge strenge i en enkelt tråd. `StringBuffer` minder om den, men er synkroniseret, så den er typisk langsommere og primært relevant for ældre, trådsikre API'er. Til formatering kan `String.format` eller template-baserede API'er være mere tydelige, men de er ikke altid hurtigere.

StringBuilder builder = new StringBuilder();

for (String item : items) {
    builder.append(item).append(',');
}

String result = builder.toString();

String message = "Hello, " + name;
Prøv at besvare dette spørgsmål med en AI-coach

Generics og collections

18Hvad er generics, og hvad er typesletning (type erasure)?

Generics gør det muligt for kode at udtrykke typesikre containere og API'er (Application Programming Interfaces), såsom List<String>. På kompileringstidspunktet kontrollerer compileren typer og indsætter typekonverteringer (casts), hvor det er nødvendigt. I kørselsmiljøet slettes størstedelen af den generiske typeinformation, så List<String> og List<Integer> deler den samme runtime-klasse. Det er grunden til, at man ikke direkte kan oprette new T(), bruge primitive typeparametre eller pålideligt tjekke instanceof List<String>.

List<String> names = new ArrayList<>();
names.add("Alice");

List<? extends Number> producer;
List<? super Integer> consumer;
Prøv at besvare dette spørgsmål med en AI-coach

19Hvad er Java Collections Framework?

Collections Framework er et sæt af grænseflader (interfaces) og implementeringer til at gemme og behandle grupper af objekter. Kernegrænseflader inkluderer `List`, `Set`, `Queue`, `Deque` og `Map`. Implementeringer inkluderer `ArrayList`, `LinkedList`, `HashSet`, `TreeSet`, `HashMap`, `TreeMap` og `PriorityQueue`. Man vælger en samling (collection) ud fra tilgangsmønster, krav til rækkefølge, unikhed, kompleksitet ved opslag, ekstra hukommelsesforbrug (memory overhead) og behov for samtidighed (concurrency).

List<String> list = new ArrayList<>();
Set<Long> ids = new HashSet<>();
Map<Long, User> usersById = new HashMap<>();
Deque<String> queue = new ArrayDeque<>();
Prøv at besvare dette spørgsmål med en AI-coach

20Hvordan fungerer `HashMap`?

`HashMap` gemmer poster i "buckets", der vælges ved hjælp af et hash af nøglen. Ved opslag beregner den hashet, finder den pågældende bucket og sammenligner nøgler med metoden `equals`. Kollisioner håndteres internt i den enkelte bucket; i moderne Java kan lange kæder omdannes til et balanceret træ (treeified), når visse betingelser er opfyldt. `HashMap` tillader én null-nøgle samt null-værdier, er ikke trådsikker (thread-safe), og den kan ændre størrelse, når tærsklen for belastningsfaktoren (load factor) overskrides.

Map<Long, User> users = new HashMap<>();
users.put(user.id(), user);

User found = users.get(user.id());
Prøv at besvare dette spørgsmål med en AI-coach

21Hvad er forskellen mellem HashMap, LinkedHashMap, TreeMap og ConcurrentHashMap?

HashMap giver hurtig usorteret adgang og er ikke trådsikker. LinkedHashMap bevarer indsættelsesrækkefølgen eller tilgangsrækkefølgen og er nyttig til LRU-lignende (Least Recently Used) cacher. TreeMap holder nøgler sorteret efter naturlig rækkefølge eller en Comparator og har O(log n) operationer. ConcurrentHashMap er designet til samtidig adgang med segmenteret/intern synkronisering, tillader ikke null-nøgler eller -værdier, og foretrækkes frem for synchronizedMap under belastning.

map.computeIfAbsent(key, ignored -> loadValue());
Prøv at besvare dette spørgsmål med en AI-coach

22Hvad er forskellen mellem ArrayList og LinkedList?

ArrayList er baseret på et array. Den giver hurtig indekseret adgang, kompakt hukommelseslayout og effektiv iterering, men indsættelse i midten kræver, at elementer rykkes. LinkedList er en dobbeltkædet liste. Den har billigere indsættelse, hvis noden allerede er kendt, men indekseret adgang er O(n), hukommelses-overheadet er højere, og cache-lokaliteten er dårligere. I mange reelle applikationer er ArrayList hurtigere, selv når der forekommer indsættelser.

Prøv at besvare dette spørgsmål med en AI-coach

23Hvad er forskellen mellem Comparable og Comparator?

Comparable definerer den naturlige rækkefølge internt i klassen via compareTo. Comparator er et separat objekt eller en lambda, der definerer ekstern sortering. Comparable er nyttig, når en type har én indlysende standardrækkefølge. Comparator er bedre, når der er flere sorteringskriterier, såsom efter navn, dato eller prioritet. compare og compareTo skal være konsistente og helst kompatible med equals, når de bruges i sorterede sets eller maps.

class User implements Comparable<User> {
    private int age;

    @Override
    public int compareTo(User other) {
        return Integer.compare(age, other.age);
    }
}

Comparator<User> byName =
    Comparator.comparing(User::getName);

Comparator<User> byAgeThenName =
    Comparator.comparingInt(User::getAge)
              .thenComparing(User::getName);
Prøv at besvare dette spørgsmål med en AI-coach

Exceptions og ressourcer

24Hvad er forskellen på checked og unchecked exceptions?

Checked exceptions nedarver fra Exception, men ikke fra RuntimeException, og compileren kræver, at den kaldende kode håndterer eller deklarerer dem. Unchecked exceptions nedarver fra RuntimeException og behøver ikke at blive angivet i metodesignaturer. Checked exceptions egner sig til situationer ved grænsefladen af et API, som programmet kan komme sig over. Unchecked exceptions egner sig til programmeringsfejl, ugyldig tilstand og brud på kontrakter. I moderne servicekode undgår teams ofte at bruge for mange checked exceptions, fordi de spreder sig støjende gennem de forskellige lag.

void load() throws IOException {
    // ...
}

throw new IllegalArgumentException("Invalid id");
Prøv at besvare dette spørgsmål med en AI-coach

25Hvad er forskellen mellem throw, throws og try-with-resources?

throw kaster øjeblikkeligt en exception. throws deklarerer en mulig exception i en metodesignatur. try-with-resources lukker automatisk AutoCloseable-ressourcer i omvendt rækkefølge. Hvis både hovedblokken og lukningen kaster exceptions, bliver den sidstnævnte gemt som en undertrykt (suppressed) exception. Dette er mere sikkert og tydeligere end manuelle finally-blokke, når der er flere ressourcer involveret.

throw new IllegalArgumentException("Invalid id");

void readFile() throws IOException {}

try (var reader = Files.newBufferedReader(path)) {
    return reader.readLine();
}
Prøv at besvare dette spørgsmål med en AI-coach

Stream API og funktionel stil

26Hvad er Stream API?

Stream API giver kode mulighed for at behandle sekvenser deklarativt. Mellemoperationer som filter, map og sorted er dovne (lazy); terminale operationer som toList, collect, reduce og forEach udløser eksekvering. En stream er ikke en samling (collection), bruges normalt kun én gang og fungerer bedst med rene funktioner uden bivirkninger. parallelStream garanterer ikke hurtigere eksekvering og kan skade ydeevnen eller korrektheden.

List<String> names = users.stream()
    .filter(User::isActive)
    .sorted(Comparator.comparing(User::getName))
    .map(User::getName)
    .toList();
Prøv at besvare dette spørgsmål med en AI-coach

27Hvad er Optional, og hvornår bør du bruge det?

Optional<T> repræsenterer eksplicit fraværet af et resultat og fungerer godt som returtype. Nyttige metoder inkluderer map, flatMap, filter, orElse, orElseGet og orElseThrow. orElse evaluerer sit argument umiddelbart (eagerly), mens supplier-funktionen i orElseGet kun kører, når værdien mangler. Optional anbefales normalt ikke til entitetsfelter, metodeparametre eller serialiserede DTO'er (Data Transfer Objects), medmindre der er en stærk årsag til det.

Optional<User> user = repository.findById(id);

String name = user
    .map(User::getName)
    .orElse("Unknown");

optional.orElse(expensiveOperation());
optional.orElseGet(() -> expensiveOperation());
Prøv at besvare dette spørgsmål med en AI-coach

Samtidighed

28Hvad garanterer synchronized, volatile og atomare klasser?

synchronized giver gensidig udelukkelse og et happens-before-forhold. volatile garanterer synlighed af de seneste skrivninger og begrænser omrokering, men det gør ikke sammensatte operationer atomare. Atomare klasser tilbyder individuelle atomare operationer såsom incrementAndGet. Brug låse eller synchronized til flere relaterede ændringer, volatile til simple tilstandsflag og AtomicInteger eller LongAdder til tællere under hård belastning (contention).

synchronized (lock) {
    counter++;
}

private volatile boolean running = true;

AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();
Prøv at besvare dette spørgsmål med en AI-coach

29Hvad er en race condition, deadlock og trådsikkerhed?

En race condition opstår, når resultatet afhænger af en uforudsigelig eksekveringsrækkefølge for tråde. En deadlock opstår, når tråde venter i det uendelige på hinandens ressourcer. For at opnå trådsikkerhed bør man reducere delt foranderlig tilstand, opretholde en konsistent låserækkefølge, forkorte kritiske sektioner, undgå at kalde ekstern kode under låse, og bruge uforanderlige objekter, samtidige samlinger, timeouts eller beskedudveksling (message passing). Trådsikkerhed er en egenskab ved hele protokollen for tilgang til tilstand.

Prøv at besvare dette spørgsmål med en AI-coach

30Hvad er forskellen mellem ExecutorService, CompletableFuture og virtuelle tråde?

ExecutorService håndterer en trådpulje og eksekvering af opgaver. CompletableFuture hjælper med at sammensætte asynkrone operationer. Virtuelle tråde er letvægts JVM-tråde (Java Virtual Machine), som er praktiske til et stort antal blokerende I/O-opgaver. De forenkler en tråd-pr-forespørgsel-model, men de fremskynder ikke CPU-krævende arbejde, fjerner ikke begrænsninger for databaser eller eksterne ressourcer og gør ikke foranderlig tilstand trådsikker automatisk.

try (var executor = Executors.newFixedThreadPool(4)) {
    Future<Integer> future = executor.submit(() -> calculate());
    int result = future.get();
}

CompletableFuture<User> userFuture =
    CompletableFuture.supplyAsync(this::loadUser)
        .thenApply(this::enrichUser)
        .exceptionally(error -> fallbackUser());

try (var executor =
         Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> handleRequest());
}
Prøv at besvare dette spørgsmål med en AI-coach