diff --git a/api.go b/api.go
index 809b9e7760edb422167e13e7a6de5c8a76ed261a..9dcbb033bd706c383c6074fdb5d12bfdadc9b4a6 100644
--- a/api.go
+++ b/api.go
@@ -3,13 +3,23 @@
 
 package configuration
 
-import "github.com/imdario/mergo"
+import (
+	"github.com/imdario/mergo"
+)
 
 // NewSetting creates a new configuration setting
 // with the given defaults.
 func New[C any](defaults C) *Settings[C] {
 
 	s := &Settings[C]{}
+
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	s.initDefaults()
 
 	if err := mergo.Merge(&defaults, s.config); err != nil {
@@ -32,6 +42,14 @@ func New[C any](defaults C) *Settings[C] {
 // Set the mnemonic
 // The mnemonic is used to identify the configuration in the configuration file
 func (s *Settings[C]) SetMnemonic(mnemonic string) *Settings[C] {
+
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	if mnemonic == "" {
 		s.errors = append(s.errors, MnemonicEmptyError)
 	} else {
diff --git a/change-handler.go b/change-handler.go
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/change.go b/change.go
index f5136700208561ac5a8272f2ebe0a8230f100d12..cb083d7804b799c6dfd85ade5439a7b893d69643 100644
--- a/change.go
+++ b/change.go
@@ -71,6 +71,13 @@ func (s *Settings[C]) setConfigInternal(config C, lock bool) *Settings[C] {
 
 	}()
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	if err := validateConfig[C](config); err != nil {
 		s.errors = append(s.errors, err)
 		return s
diff --git a/copyable.go b/copyable.go
index 1fde765b46b0ec3eaf509b893430e050afe0c1c3..df98baf6ada81073d75fee01ac6884d669f90c23 100644
--- a/copyable.go
+++ b/copyable.go
@@ -12,6 +12,13 @@ func (s *Settings[C]) Copy(m map[string]any) {
 
 	c := s.Config()
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	for k, v := range m {
 		err := pathfinder.SetValue(&c, k, v)
 		if err != nil {
diff --git a/env.go b/env.go
index 50ac0d1f63fec8725dbdaf88a2f09fe53cf6aa48..72719309da177bbef851ba3a6bb24bf4ae949db4 100644
--- a/env.go
+++ b/env.go
@@ -14,6 +14,13 @@ func (s *Settings[C]) InitFromEnv(prefix string) *Settings[C] {
 	s.Lock()
 	defer s.Unlock()
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	err := runOnTags(&s.config, []string{envTagKey}, func(k string, field reflect.Value) {
 
 		if !field.CanSet() {
diff --git a/error-handler.go b/error-handler.go
new file mode 100644
index 0000000000000000000000000000000000000000..91f3ae462a4f001e9815cf4378e1a53adadfafc0
--- /dev/null
+++ b/error-handler.go
@@ -0,0 +1,105 @@
+// Copyright 2022 schukai GmbH
+// SPDX-License-Identifier: AGPL-3.0
+
+package configuration
+
+import (
+	"fmt"
+	"github.com/r3labs/diff/v3"
+)
+
+type ChangeEvent struct {
+	Changlog diff.Changelog
+}
+
+type EventHook interface {
+	Handle(event ChangeEvent)
+}
+
+// OnChange registers a hook that is called when the configuration changes.
+func (s *Settings[C]) OnChange(hook EventHook) *Settings[C] {
+	s.hooks.change = append(s.hooks.change, hook)
+	return s
+}
+
+// HasOnChangeHook returns true if there are registered hooks.
+func (s *Settings[C]) HasOnChangeHook(hook EventHook) *Settings[C] {
+	for _, h := range s.hooks.change {
+		if h == hook {
+			break
+		}
+	}
+	return s
+}
+
+// RemoveOnChangeHook removes a change hook from the list of hooks.
+func (s *Settings[C]) RemoveOnChangeHook(hook EventHook) *Settings[C] {
+	for i, h := range s.hooks.change {
+		if h == hook {
+			s.hooks.change = append(s.hooks.change[:i], s.hooks.change[i+1:]...)
+			break
+		}
+	}
+	return s
+}
+
+func (s *Settings[C]) notifyChangeHooks(changelog diff.Changelog) *Settings[C] {
+	for _, h := range s.hooks.change {
+		h.Handle(ChangeEvent{Changlog: changelog})
+	}
+	return s
+}
+
+func (s *Settings[C]) setConfigInternal(config C, lock bool) *Settings[C] {
+
+	var (
+		changelog diff.Changelog
+		err       error
+	)
+
+	if lock {
+		s.Lock()
+	}
+
+	defer func() {
+		if lock {
+			s.Unlock()
+		}
+
+		if len(changelog) > 0 {
+			go s.notifyChangeHooks(changelog)
+		}
+
+		fmt.Println(">>>>>>>>> setConfigInternal", len(changelog))
+		fmt.Println(changelog)
+		fmt.Println(s.errors)
+
+	}()
+
+	if err := validateConfig[C](config); err != nil {
+		s.errors = append(s.errors, err)
+		return s
+	}
+
+	d, err := diff.NewDiffer()
+	if err != nil {
+		s.errors = append(s.errors, err)
+		return s
+	}
+
+	d.ConvertCompatibleTypes = true
+	d.AllowTypeMismatch = true
+
+	changelog, err = d.Diff(s.config, config)
+	if err != nil {
+		s.errors = append(s.errors, err)
+		return s
+	}
+
+	s.config = config
+	return s
+}
+
+func (s *Settings[C]) SetConfig(config C) *Settings[C] {
+	return s.setConfigInternal(config, true)
+}
diff --git a/error-handler_test.go b/error-handler_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/export.go b/export.go
index bbeba7f0482c2beba1355d4a81865037957cfdbb..0c57483150b4a4d704d844a4006abf4f183900bc 100644
--- a/export.go
+++ b/export.go
@@ -34,6 +34,13 @@ func (s *Settings[C]) writeProperties(writer io.Writer) error {
 
 	m, errors := getMapForProperties[C](s.config)
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	if len(errors) > 0 {
 		for _, err := range errors {
 			s.errors = append(s.errors, err)
@@ -53,6 +60,13 @@ func (s *Settings[C]) WriteFile(fn string, format Format) *Settings[C] {
 
 	var file *os.File
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	if fn == "" {
 		file = os.Stdout
 	} else {
@@ -73,6 +87,13 @@ func (s *Settings[C]) Write(writer io.Writer, format Format) *Settings[C] {
 
 	var err error
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	switch format {
 	case Json:
 		err = s.writeJson(writer)
diff --git a/file.go b/file.go
index f3161deb01ad3fabd0de8f619195b79f8758c053..252529e71ad892f1e6e9bfa70a3cda00334d075e 100644
--- a/file.go
+++ b/file.go
@@ -38,6 +38,13 @@ func (s *Settings[C]) AddFile(file string, format ...Format) *Settings[C] {
 
 	var f Format
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	if format == nil || len(format) == 0 {
 		f = RecogniseFormat
 	} else if format != nil && len(format) == 1 {
@@ -104,6 +111,13 @@ func (s *Settings[C]) AddDirectory(d string) *Settings[C] {
 
 func (s *Settings[C]) sanitizeDirectories() {
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	wd, err := os.Getwd()
 	if err != nil {
 		s.errors = append(s.errors, err)
@@ -146,9 +160,17 @@ func (s *Settings[C]) AddWorkingDirectory() *Settings[C] {
 	s.Lock()
 	defer s.Unlock()
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	current, err := os.Getwd()
 	if err != nil {
 		s.errors = append(s.errors, err)
+
 		return s
 	}
 	s.files.directories = append(s.files.directories, current)
@@ -161,6 +183,13 @@ func (s *Settings[C]) AddEtcDirectory() *Settings[C] {
 	s.Lock()
 	defer s.Unlock()
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	if s.mnemonic == "" {
 		s.errors = append(s.errors, MnemonicEmptyError)
 		return s
@@ -183,6 +212,16 @@ func (s *Settings[C]) AddEtcDirectory() *Settings[C] {
 // The mnemonic must be set for this function
 func (s *Settings[C]) AddUserConfigDirectory() *Settings[C] {
 
+	s.Lock()
+	defer s.Unlock()
+
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	if s.mnemonic == "" {
 		s.errors = append(s.errors, MnemonicEmptyError)
 		return s
@@ -214,6 +253,13 @@ func (s *Settings[C]) SetDefaultDirectories() *Settings[C] {
 
 func (s *Settings[C]) SetFileFormat(format Format) *Settings[C] {
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	if slices.Contains(availableFormats, format) {
 		s.files.format = format
 	} else {
@@ -226,6 +272,13 @@ func (s *Settings[C]) SetFileFormat(format Format) *Settings[C] {
 // Set the file name without extension
 func (s *Settings[C]) SetFileName(name string) *Settings[C] {
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	if name == "" {
 		s.errors = append(s.errors, FileNameEmptyError)
 	} else {
diff --git a/flags.go b/flags.go
index 904947ba41fcd5c2df2506582b94e068eceece0e..0fb13a8a3d9b50dfcf0a7836bf488c61918ddb75 100644
--- a/flags.go
+++ b/flags.go
@@ -13,6 +13,13 @@ import (
 // The file is read from the flag specified by the name
 func (s *Settings[C]) AddFileFromFlagSet(flagset *flag.FlagSet, name string, format Format) *Settings[C] {
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	flag := flagset.Lookup(name)
 	if flag == nil {
 		s.errors = append(s.errors, newFlagDoesNotExistError(name))
@@ -33,6 +40,13 @@ func (s *Settings[C]) InitFromFlagSet(flagset *flag.FlagSet) *Settings[C] {
 	s.Lock()
 	defer s.Unlock()
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	err := runOnTags(&s.config, []string{flagTagKey}, func(k string, field reflect.Value) {
 
 		flag := flagset.Lookup(k)
diff --git a/import.go b/import.go
index 6ec9e29dc641513eeaeeb0d77de0f029f48566da..074a4132819db11f2f0c52f17e859034f3407574 100644
--- a/import.go
+++ b/import.go
@@ -49,6 +49,13 @@ func (s *Settings[C]) importStream(r reader) {
 	var c C
 	var err error
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	err = nil // reset error
 
 	x := r.reader
@@ -128,6 +135,13 @@ func (s *Settings[C]) Import() *Settings[C] {
 	s.Lock()
 	defer s.Unlock()
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	defaults := s.config
 
 	var n C
diff --git a/settings.go b/settings.go
index ff3506767fcf76e6bf879c14dcfc2278417f3756..12d57bdbd1edf116ac65b3bc06e572aab26bacfa 100644
--- a/settings.go
+++ b/settings.go
@@ -36,6 +36,13 @@ type Settings[C any] struct {
 
 func (s *Settings[C]) initDefaults() *Settings[C] {
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	err := runOnTags(&s.config, []string{"default"}, func(v string, field reflect.Value) {
 
 		if field.CanSet() {
diff --git a/watch.go b/watch.go
index a1d3d7ac18739f14091b5ab9372852abaed45914..950304861360e4c443f049c7392ede751308906b 100644
--- a/watch.go
+++ b/watch.go
@@ -11,6 +11,13 @@ func (s *Settings[C]) initWatch() *Settings[C] {
 
 	var err error
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	if s.fileWatch.watcher != nil {
 		s.errors = append(s.errors, WatchAlreadyInitializedError)
 		return s
@@ -31,6 +38,13 @@ func (s *Settings[C]) StopWatching() *Settings[C] {
 	s.fileWatch.Lock()
 	defer s.fileWatch.Unlock()
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	if s.fileWatch.watcher == nil {
 		s.errors = append(s.errors, WatchNotInitializedError)
 		return s
@@ -52,6 +66,13 @@ func (s *Settings[C]) Watch() *Settings[C] {
 	s.fileWatch.Lock()
 	defer s.fileWatch.Unlock()
 
+	errorCount := len(s.errors)
+	defer func() {
+		if len(s.errors) > errorCount {
+			s.notifyErrorHooks()
+		}
+	}()
+
 	if s.fileWatch.watcher == nil {
 		s.initWatch()
 	}